Extracting Curved Contours
So I have been experimenting with OpenCV and extracting a specific portion from an image with python.
My test image is:
From this image, I want to draw the contours around that stockpile of soil as follows:
(Note: This has been done in Paint)
I have tried following things:
- Reading the Image and converting it into Grayscale
- Morphological Transformation and thresholding as follows:
But I am not sure what to do next? How to extract the aforementioned part of the image?
import cv2
img = cv2.imread('Feature Extraction/soil_stockpile_test_image.png',
cv2.IMREAD_GRAYSCALE)
blur = cv2.GaussianBlur(img, (1, 1), 2)
h, w = img.shape[:2]
# Morphological gradient
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
gradient = cv2.morphologyEx(blur, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('Morphological gradient', gradient)
#Contours
thresh = cv2.threshold(img,150, 255,cv2.THRESH_BINARY_INV)[1]
cnts, h = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.imshow('thresh', thresh)
I tried the histogram back projection method. It somewhat worked but it still shows some unwanted part of the image. I just want the stockpile to be shown in the result.
Here is the code and the resulting image:
# Original Image
orig_img = cv2.imread('Feature Extraction/soil_stockpile_test_image.png')
roi = cv2.imread('Feature Extraction/roi_soil.png')
# HSV Image
hsv_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2HSV)
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
# Region of Interest
roi_hist = cv2.calcHist([hsv_roi], [0, 1], None, [180, 256], [0, 180, 0, 256])
mask = cv2.calcBackProject([hsv_img], [0, 1], roi_hist, [0, 180, 0, 256], 1)
# Remove Noise
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
mask = cv2.filter2D(mask, -1, kernel)
_, mask = cv2.threshold(mask, 210, 255, cv2.THRESH_BINARY)
# Merge and perform bitwise operation
mask = cv2.merge((mask, mask, mask))
result = cv2.bitwise_and(orig_img, mask)
#cv2.imshow('HSV Image', hsv_img)
#cv2.imshow('Region of Interest', roi)
#cv2.imshow('Mask', mask)
cv2.imshow('Result', result)
Result:
Edit 1:
After using the grab cut algorithm, with the following code, this is the result I obtained:
# Grab Cut Mask
img = cv2.imread('Feature Extraction/soil_stockpile_test_image.png')
# These params have been hardcoded. Need to be changed for every image.
top_left_x = 300
top_left_y = 150
bottom_right_x = 600
bottom_right_y = 350
Green_color = (0, 255, 0)
# Draw the rectangle
output = cv2.rectangle(img,
(top_left_x, top_left_y),
(bottom_right_x, bottom_right_y),
color=Green_color, thickness=3)
#cv2.imwrite('Output_Image_with_Rectangle.jpg', output)
#cv2.imshow('Output Image with Rectangle', output)
# Grab Cut Mask
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (top_left_x, top_left_y, bottom_right_x, bottom_right_y)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
new_img = img*mask2[:,:,np.newaxis]
cv2.imwrite('Grab_Cut_img.jpg', new_img)
plt.imshow(new_img),plt.colorbar(),plt.show()
But there is still some black part there. Can I resize this grab cut image? Also, How do i do that? Also, in the image, you can see that there is still some background. How do I suppress this?
I really need your help. Thanks.
looks good. What result would you desire via automation? Would you refine by angularity? Or manual feedback loop (GrabCut mask?). Or is it always in the corner like a watermark logo? Would mask opening remove enough "noise" ?
After you apply your mask, apply Hough Lines to said mask for contour separation. Then find and mask the minimum enclosing rectangle of... ??? The largest area or the contour in a position near x/y. You could expand the canvas with CopyMakeBorder for better estimation?
Depth sensor? Re-take photo? Hire assistant?
@kpachinger Thanks for your reply but I don't quite understand what you mean by refine by angularity. I have not done this before.
@kpachinger I used the Grab Cut algorithm and the result can be seen in Edit 1 in the question. But there is still some background in the mask. How do i supress that?
You should GC_INIT_WITH_MASK, and your mask needs correct GC values. Initial progress from mask is your intent as opposed to a uniform rect area.
Consider that GrabCut is now expected to discern a pile from similar nearby features. It will be determined by rgba area continuity, and provided mask values.
Accurate and consistent marks on GC mask will affect "success". The benefit of defining the pile at it's angle or enclosing rectangle is a better in/out.
The question is what degree of correct value can you give? Without manually negating the purpose?
So you mean I should replace GC_INIT_WITH_RECT with GC_INIT_WITH_MASK and use the mask I had created previously and not the mask created for the Grabcut? But If I do that I get the follwing error: (-215:Assertion failed) !bgdSamples.empty() && !fgdSamples.empty() in function 'initGMMs'
Yes. The GrabCut accepts 4 specific mat values. They are assigned by name or number, and are not a linear range but a code.
A pixel set to GC_FGD or GC_BGD is decided, and it affects neighboring pixels less than PR_FGD and PR_BGD when GrabCut considers foreground/background. That is my experience.
It is possible that GrabCut completes without certainty of foreground/background. Consider an image of evenly distributed noise. The resulting mask, white or black, would impact later outcomes. Would Grabcut flip a coin or should you personally handle it?
It is possible to use a pipe for the init, to allow MASK | RECT. This allows more logic and less critical errors. Perhaps you put mask/grabcut through a feedback loop and mark the mask multiple times until satisfied?
@YashRunwal. Comment out
np.zeros
. U don't needed this.@supra56 Do you mean the masks and bg and fgmodel?
I am still not able to 100% solve this. Any help is appreciated. Thank You.