To explain the question a bit. I have an image that already contains a white bounding box as shown here:
Input image
What I need is to crop the part of the image surrounded by the bounding box.
FindContours doesn't seem to work here so I tried something using the following code:
import cv2
import numpy as np
bounding_box_image = cv2.imread('PedestrianRectangles/1/grim.pgm')
edges = cv2.Canny(bounding_box_image, 50, 100) # apertureSize=3
cv2.imshow('edge', edges)
cv2.waitKey(0)
lines = cv2.HoughLinesP(edges, rho=0.5, theta=1 * np.pi / 180,
threshold=100, minLineLength=100, maxLineGap=50)
# print(len(lines))
for i in lines:
for x1, y1, x2, y2 in i:
# print(x1, y1, x2, y2)
cv2.line(bounding_box_image, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imwrite('houghlines5.jpg', bounding_box_image)
without any success. Playing around with the parameters didn't help too much either. The result of my code snippet is show on the following image:
Output
I had the idea to do cropping after the line detection etc.
I am relatively new to opencv so help would be appreciated. Is there a good or easy approach to this problem that I'm missing? Googling around didn't help so any links, code snippets would be helpful.
Thanks to Silencer, with his help I was able to make it work, so I'll provide the code and hope it helps other people:
import cv2
import numpy as np
bounding_box_image = cv2.imread('PedestrianRectangles/1/grim.pgm')
grayimage = cv2.cvtColor(bounding_box_image, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(grayimage, 254, 255, cv2.THRESH_BINARY)
cv2.imshow('mask', mask)
cv2.waitKey(0)
image, contours, hierarchy = cv2.findContours(mask, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
if cv2.contourArea(contour) < 200:
continue
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect)
ext_left = tuple(contour[contour[:, :, 0].argmin()][0])
ext_right = tuple(contour[contour[:, :, 0].argmax()][0])
ext_top = tuple(contour[contour[:, :, 1].argmin()][0])
ext_bot = tuple(contour[contour[:, :, 1].argmax()][0])
roi_corners = np.array([box], dtype=np.int32)
cv2.polylines(bounding_box_image, roi_corners, 1, (255, 0, 0), 3)
cv2.imshow('image', bounding_box_image)
cv2.waitKey(0)
cropped_image = grayimage[ext_top[1]:ext_bot[1], ext_left[0]:ext_right[0]]
cv2.imwrite('crop.jpg', cropped_image)
And the output
Threshold to binary # 250
Find contours in the binary
Filter the contour by height/width of boundingRect
Related
So I've been trying to make bounding boxes around a couple of fruits that I made in paint. I'm a total beginner to opencv so I watched a couple tutorials and the code that I typed made, makes contours around the object and using that I create bounding boxes. However it makes way too many tiny contours and bounding boxes. For example, heres the initial picture:
and heres the picture after my code runs:
However this is what I want:
Heres my code:
import cv2
import numpy as np
img = cv2.imread(r'C:\Users\bob\Desktop\coursera\coursera-2021\project2\fruits.png')
grayscaled = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(grayscaled, (7,7), 1)
canny = cv2.Canny(blur, 50, 50)
img1 = img.copy()
all_pics = []
contours, Hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02*peri, True)
x, y, w, h = cv2.boundingRect(approx)
bob = img [y:y+h, x:x+w]
all_pics.append((x, bob))
cv2.rectangle(img1, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow("title", img1)
cv2.waitKey(0)
I think FloodFill in combination with FindContours can help you:
import os
import cv2
import numpy as np
# Read original image
dir = os.path.abspath(os.path.dirname(__file__))
im = cv2.imread(dir+'/'+'im.png')
# convert image to Grayscale and Black/White
gry = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
cv2.imwrite(dir+'/im_1_grayscale.png',gry)
bw=cv2.threshold(gry, 127, 255, cv2.THRESH_BINARY)[1]
cv2.imwrite(dir+'/im_2_black_white.png',bw)
# Use floodfill to identify outer shape of objects
imFlood = bw.copy()
h, w = bw.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
cv2.floodFill(imFlood, mask, (0,0), 0)
cv2.imwrite(dir+'/im_3_floodfill.png',imFlood)
# Combine flood filled image with original objects
imFlood[np.where(bw==0)]=255
cv2.imwrite(dir+'/im_4_mixed_floodfill.png',imFlood)
# Invert output colors
imFlood=~imFlood
cv2.imwrite(dir+'/im_5_inverted_floodfill.png',imFlood)
# Find objects and draw bounding box
cnts, _ = cv2.findContours(imFlood, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
for cnt in cnts:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02*peri, True)
x, y, w, h = cv2.boundingRect(approx)
bob = im[y:y+h, x:x+w]
cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Save final image
cv2.imwrite(dir+'/im_6_output.png',im)
Grayscale and black/white:
Add flood fill:
Blend original objects with the flood filled version:
Invert the colors of blended version:
Final output:
his guys answer was just what I was looking for. If anyone runs through this problem try this:
Draw contours around objects with OpenCV
I'm not sure if I encountered a bug or if I'm missing something (opencv 4.4.0.46 and 4.5.3.56, maybe others).
I'm trying to find the rotated bounding box for this image:
This is the result:
here is the code:
import cv2
import numpy as np
base_image = cv2.imread("so_sample.png", 0)
thresh = cv2.threshold(base_image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
coords = np.column_stack(np.where(thresh > 0))
rect = cv2.minAreaRect(coords)
print("RECT", rect)
box = np.int0(cv2.boxPoints(rect))
drawImg = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
drawImg = cv2.copyMakeBorder(drawImg, 0, 100, 0, 100, cv2.BORDER_REPLICATE) # to see the whole box
cv2.drawContours(drawImg, [box], 0,(0,0,255), 2)
cv2.imshow("base_image", base_image)
cv2.imshow("thresh", thresh)
cv2.imshow("drawImg", drawImg)
cv2.waitKey(0)
This code works fine for the "thunder" sample image and it looks like all the sample code I could find around. Am I missing something? Thanks
You are using np.column_stack(np.where(thresh > 0)) to find the contours.
OpenCV uses (x, y) notation whereas NumPy uses (row, col).
You can check it by printing coords.
I suggest using OpenCV functions where possible.
The following code works.
coords, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rect = cv2.minAreaRect(np.vstack(coords))
See Contour Features.
I have cropped the original photo to the surrounding area but it is dependent on the mouse position as well and will be different every time. Therefore I do not want a static crop of dimensions as it will vary.
The contours do not want to detect the rectangle I want because it is an incomplete rectangle since there is a cursor in the way.
Currently, my code is able to detect the contours but will only grab the outer rectangle and make a small crop.
I would like to make it detect the inner rectangle, do I have to somehow complete the rectangle first?
Source image
Contours detected
What I would LIKE to crop
What it is currently cropping
import cv2
import imutils # https://pypi.org/project/imutils/
import numpy as np
...
def crop_hover_list(cursor_coord_x, cursor_coord_y):
img_test = cv2.imread("source.jpg")
grayimage = cv2.cvtColor(img_test, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(grayimage, 235, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(img_test, contours, -1, (0,255,0), 3)
cv2.imshow("test", img_test)
cv2.waitKey(0)
for contour in contours:
if cv2.contourArea(contour) < 250:
continue
print(list(contour))
ext_left = tuple(contour[contour[:, :, 0].argmin()][0])
ext_right = tuple(contour[contour[:, :, 0].argmax()][0])
ext_top = tuple(contour[contour[:, :, 1].argmin()][0])
ext_bot = tuple(contour[contour[:, :, 1].argmax()][0])
(x,y,w,h) = cv2.boundingRect(contour)
cv2.rectangle(img_test, (x, y), (x + w, y + h), (0, 0, 255), 3)
cv2.imshow("test", img_test)
cv2.waitKey(0)
I have your test image in images/ folder. Change it as you need to test the script.
img_test = cv2.imread("images/MCeLz.png")
Please pay attention here:
ret ,contours, hierarchy = cv2.findContours ...
I need 'ret' to work in my opencv version 3.2. May be you not.
My solution is use 2 parameters:
minimum area as you did , and color in a range. Then check that cursor is inside the contour.
import cv2
import imutils # https://pypi.org/project/imutils/
import numpy as np
def crop_hover_list(cursor_coord_x, cursor_coord_y):
img_test = cv2.imread("images/MCeLz.png")
cimg = img_test.copy()
mask = cv2.inRange(img_test, (200,200,200), (235, 235,235))
cv2.imshow("test", img_test)
cv2.imshow('mask',mask)
ret ,contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
for contour in contours:
if cv2.contourArea(contour) < 2000:
continue
(x,y,w,h) = cv2.boundingRect(contour)
is_cursor_inside = x <= cursor_coord_x <= (x+w) and y <= cursor_coord_y <= (y+h)
if is_cursor_inside:
cv2.rectangle(cimg, (x, y), (x + w, y + h), (0, 0, 255), 4)
cv2.drawContours(cimg, [contour], 0, (0,255,0), 3)
crop = img_test[y:y+h,x:x+w]
cv2.imshow("crop", crop)
cv2.imshow("cimg", cimg)
cv2.imshow("test", img_test)
cv2.waitKey(0)
crop_hover_list(300, 50)
We are making a game where you can use grid paper to make your own level.
The first step was detecting the boundaries of the level, so I made a thicker black border around the level for the edges.
This is the image: https://imgur.com/a/Wx3p7Ci
I first edited the image to gray, applied GaussianBlur and adaptiveThreshold.
This is the result: https://imgur.com/a/dp0G4my
Normal Houghlines isn't really an option I think as it detects so many lines and loses the thicker lines.
findContour I haven't got a usable outcomes as there are so many shapes in a gridpaper.
So I ended up with HoughLinesP which resulted in this:https://imgur.com/a/yvIpYNy
So it detects one part of the rectangle correctly and that is it. I would have another idea which would rely on colors which isn't ideal (people would need a specific color) but I'm at a loss on what a good approach for this would be.
Here is my code I currently have for HoughLinesP
import cv2 as cv
import numpy as np
# from openCV.main import contours
image = cv.imread("grey-grid-blackborder.jpg")
cv.imshow("Image", image)
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("Gray", gray)
blur = cv.GaussianBlur(gray, (5, 5), 0)
cv.imshow("blur", blur)
thresh = cv.adaptiveThreshold(blur, 255, 1, 1, 11, 2)
cv.imshow("thresh", thresh)
grid = image
minLineLength = 1
maxLineGap = 0
# lines = cv.HoughLinesP(thresh, 1, np.pi / 180, 100, minLineLength, maxLineGap)
lines = cv.HoughLinesP(thresh, 1, np.pi / 180, 100, minLineLength=minLineLength, maxLineGap=maxLineGap)
for x1, y1, x2, y2 in lines[0]:
cv.line(grid, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv.imshow("grid", grid)
cv.waitKey(0)
I try to detect the yellow line in the following picture but shadows obscured the yellow roads. Do we have any method to deal with that?
We can detect the yellow in this question:About Line detection by using OpenCV and How to delete the other object from figure by using opencv?.
The coding is as follows:
import cv2
import numpy as np
image = cv2.imread('Road.jpg')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
low_yellow = np.array([18, 94, 140])
up_yellow = np.array([48, 255, 255])
mask = cv2.inRange(hsv, low_yellow, up_yellow)
edges = cv2.Canny(mask, 75, 150)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, maxLineGap=250)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(image, (x1, y1), (x2, y2), (0, 255, 0), 5)
# cv2.imshow('image', img)
cv2.imwrite("result.jpg", edges)
Here is the code to convert to lab and auto-threshold
You'll have to detect the lines using a proper method.
An advanced solution would be training a dataset to do segmentation (neural network Ex : Unet )
import cv2
import numpy as np
img = cv2.imread('YourImagePath.jpg')
cv2.imshow("Original", img)
k = cv2.waitKey(0)
# Convert To lab
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# display b channel
cv2.imshow("Lab", lab[:, :, 2])
k = cv2.waitKey(0)
# auto threshold using Otsu
ret , mask = cv2.threshold(lab[:, :, 2] , 0 , 255 , cv2.THRESH_BINARY+
cv2.THRESH_OTSU)
#display Binary
cv2.imshow("Binary", mask)
k = cv2.waitKey(0)
cv2.destroyAllWindows()
Output: