OpenCV From Canny edges to contours - opencv

I have a edge detected by Canny.
And I want to extract contours of the edges.
I have checked the following post.
OpenCV converting Canny edges to contours.
But it didn't deal with complex shape. e.g, circle with rectangle or circle with line.
cv::findContours() function has 2 issues.
1. Return closed contour for non closed edge, but I want non closed contour
2. Return 2 closed contours for closed edge(maybe one of the contours is for edge, and another one is for inner side of the edge, but I want one of the two.
Is there any way to solve this out?
Thanks.
PS : I have uploaded the sample image.

It all depends on the parameters you choose while finding contours.
In OpenCV you can find contours using
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
and draw them
cv2.drawContours(im, contours, -1, (0, 255, 0), -1) #---set the last parameter to -1

Related

OpenCV: How to detect nested geometrical shapes?

Apologies, I'm new to OpenCV. How to detect nested geometrical shapes in OpenCV?
I got this answer about outer shapes, but I need something like a triangle in a square kind of thing. Also, is there a way to make it work with rounded corners? Example:
Try this code for finding contours
import cv2
img = cv2.imread('shapes.png', 0)
thresh = cv2.threshold(img, 60, 255, cv2.THRESH_BINARY_INV)[1]
cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
In the code above the image is read in gray format. Then while thresholding the format used is binary_INV because we want the background as black and the foreground as white before finding contours. Your displayed test image has the opposite. Now while finding contours you will need to use RETR_TREE and not RETR_EXTERNAL because the latter only finds the external contours where as the former will find all contours. Now you use any of the links provided for finding sides.

Billboard corner detection

I was trying to detect billboard images on a random background. I was able to localize the billboard using SSD, this give me approximate bounding box around the billboard. Now I want to find the exact corners of the billboard for my application. I tried using different strategies which I came across such as Harris corner detection (using Opencv), finding intersections of lines using, Canny + morphological operations + contours. The details on the output is given below.
Harris corner detection
The pseudocode for the harris corner detection is as follows:
img_patch_gray = np.float32(img_patch_gray)
harris_point = cv2.cornerHarris(img_patch_gray,2,3,0.04)
img_patch[harris_point>0.01*harris_point.max()]=[255,0,0]
plt.figure(figsize=IMAGE_SIZE)
plt.imshow(img_patch)
Here the red dots are the corners detected by the Harris corner detection algorithm and the points of interest are encircled in green.
Using Hough line detection
Here I was trying to find the intersection of the lines and then choosing the points. Something similar to stackoverflow link, but it is very difficult to get the exact lines since billboards have text and graphics in it.
Contour based
In this approach I have used canny edge detector, followed by dilation(3*3 kernel), followed by contour.
bin_img = cv2.Canny(gray_img_patch,100,250)
bin_img = dilate(bin_img, 3)
plt.imshow(bin_img, cmap='gray')
(_,cnts, _) = cv2.findContours(bin_img.copy(),
cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:10]
cv2.drawContours(img_patch, [cnts[0]],0, (0,255,0), 1)
, . I had tried using approxPolyDp function from openCV but it was not as expected since it can also approximate larger or smaller contours by four points and in some images it might not form contours around the billboard frame.
I have used openCV 3.4 for all the image processing operations. used can be found here. Please note that the image discussed here is just for the illustration purpose and in general image can be of any billboard.
Thanks in advance, any help is appreciated.
This is a very difficult task because the image containes a lot of noise. You can get an approximation of the contour but specific corners would be very hard. I have made an example on how I would make an approximation. It may not work on other images. Maybe it will help a bit or give you a new idea. Cheers!
import cv2
import numpy as np
# Read the image
img = cv2.imread('billboard.png')
# Blur the image with a big kernel and then transform to gray colorspace
blur = cv2.GaussianBlur(img,(19,19),0)
gray = cv2.cvtColor(blur,cv2.COLOR_BGR2GRAY)
# Perform histogram equalization on the blur and then perform Otsu threshold
equ = cv2.equalizeHist(gray)
_, thresh = cv2.threshold(equ,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Perform opening on threshold with a big kernel (erosion followed by dilation)
kernel = np.ones((20,20),np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# Search for contours and select the biggest one
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
# Make a hull arround the contour and draw it on the original image
mask = np.zeros((img.shape[:2]), np.uint8)
hull = cv2.convexHull(cnt)
cv2.drawContours(mask, [hull], 0, (255,255,255),-1)
# Search for contours and select the biggest one again
_, thresh = cv2.threshold(mask,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
# Draw approxPolyDP on the image
epsilon = 0.008*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
cv2.drawContours(img, [cnt], 0, (0,255,0), 5)
# Display the image
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Detect two intersecting rectangles separately in opencv

I can detect rectangles that are separate from each other. However, I am having problems with rectangles in contact such as below:
Two rectangles in contact
I should detect 2 rectangles in the image. I am using findContours as expected and I have tried various modes:CV_RETR_TREE, CV_RETR_LIST. I always get the outermost single contour as shown below:
Outermost contour detected
I have tried with or without canny edge detection. What I do is below:
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3,3));
cv::erode(__mat,__mat, element);
cv::dilate(__mat,__mat, element);
// Find contours
std::vector<std::vector<cv::Point> > contours;
cv::Mat coloredMat;
cv::cvtColor(__mat, coloredMat, cv::COLOR_GRAY2BGR);
int thresh = 100;
cv::Mat canny_output;
cv::Canny( __mat, canny_output, thresh, thresh*2, 3 );
cv::findContours(canny_output, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
How can I detect both two rectangles separately?
If you already know the dimensions of the rectangle, you can use generalizedHoughTransform
If the dimensions of the rectangles are not known, you can use distanceTransform. The local maxima will give you the center location as well as the distance from the center to the nearest edge (which will be equal to half the short side of your rect). Further processing with corner detection / watershed and you should be able to find the orientation and dimensions (though this method may fail if the two rectangles overlap each other by a lot)
simple corner detection and brute force search (just try out all possible rectangle combinations given the corner points and see which one best matches the image, note that a rectangle can be defined given only 3 points) might also work

Extract single line contours from Canny edges

I'd like to extract the contours of an image, expressed as a sequence of point coordinates.
With Canny I'm able to produce a binary image that contains only the edges of the image. Then, I'm trying to use findContours to extract the contours. The results are not OK, though.
For each edge I often got 2 lines, like if it was considered as a very thin area.
I would like to simplify my contours so I can draw them as single lines. Or maybe extract them with a different function that directly produce the correct result would be even better.
I had a look on the documentation of OpenCV but I was't able to find anything useful, but I guess that I'm not the first one with a similar problem. Is there any function or method I could use?
Here is the Python code I've written so far:
def main():
img = cv2.imread("lena-mono.png", 0)
if img is None:
raise Exception("Error while loading the image")
canny_img = cv2.Canny(img, 80, 150)
contours, hierarchy = cv2.findContours(canny_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
scale = 10
contours_img = cv2.resize(contours_img, (0, 0), fx=scale, fy=scale)
for cnt in contours:
color = np.random.randint(0, 255, (3)).tolist()
cv2.drawContours(contours_img,[cnt*scale], 0, color, 1)
cv2.imwrite("canny.png", canny_img)
cv2.imwrite("contours.png", contours_img)
The scale factor is used to highlight the double lines of the contours.
Here are the links to the images:
Lena greyscale
Edges extracted with Canny
Contours: 10x zoom where you can see the wrong results produced by findContours
Any suggestion will be greatly appreciated.
If I understand you right, your question has nothing to do with finding lines in a parametric (Hough transform) sense.
Rather, it is an issue with the findContours method returning multiple contours for a single line.
This is because Canny is an edge detector - that means it is filter attuned to the image intensity gradient which occurs on both sides of a line.
So your question is more akin to: “how can I convert low-level edge features to single line?”, or perhaps: “how can I navigate the contours hierarchy to detect single lines?"
This is a fairly common topic - and here is a previous post which proposed one solution:
OpenCV converting Canny edges to contours

OpenCV converting Canny edges to contours

I have an OpenCV application fed from a webcam stream of an office interior (lot's of details) where I have to find an artificial marker. The marker is a black square on white background. I use Canny to find edges and cvFindContours for contouring, then approxPolyDP and co. for filtering and finding candidates, then use local histogram to filter further, bla bla bla...
This works more or less, but not exactly how I want. FindContours always returns a closed loop, even if Canny creates a non-closed line. I get a contour walking on both sides of the line forming a loop. For closed edges on the Canny image (my marker), I get 2 contours, one on the inside, and an other on the outside.
I have to problems with this operation:
I get 2 contours for each marker (not that serious)
the most trivial filtering is not usable (reject non-closed contours)
So my question: is it possible to get non-closed contours for non-closed Canny edges?
Or what is the standard way to solve the above 2 issues?
Canny is a very good tool, but I need a way convert the 2D b/w image, into something easily process-able. Something like connected components listing all pixels in walking order of the component. So I can filter for loops, and feed it into approxPolyDP.
Update: I missed some important detail: the marker can be in any orientation (it's not front facing the camera, no right angles), in fact what I'm doing is 3D orientation estimation, based on the 2D projection of the marker.
I found a clean and easy solution for the 2 issues in the question. The trick is enable 2 level hierarchy generation (in findCountours) and look for contours which have a parent. This will return the inner contour of closed Canny edges and nothing more. Non-closed edges are discarded automatically, and each marker will have a single contour.
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(CannyImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE, Point(0,0) );
for (unsigned int i=0; i<contours.size(); i++)
if (hierarchy[i][3] >= 0) //has parent, inner (hole) contour of a closed edge (looks good)
drawContours(contourImage, contours, i, Scalar(255, 0, 0), 1, 8);
It also works the other way around, that is: look for contours which have a child (hierarchy[i][2] >= 0), but in my case the parent check yields better results.
I had the same problem with duplicate contours and even dilate and erode could not solve it:
Mat src=imread("E:\\test.bmp"),gry,bin,nor,dil,erd;
GaussianBlur( src, nor, Size(5,5),0 );
cvtColor(nor,gry,CV_BGR2GRAY);
Canny(gry,bin,100,150,5,true);
dilate(bin,dil,Mat());
erode(dil,erd,Mat());
Mat tmp=bin.clone();
vector<vector<Point>> conts;
vector<Vec4i> hier;
findContours(tmp,conts,hier,CV_RETR_TREE,CV_CHAIN_APPROX_SIMPLE);
This image (test.bmp) contains 3 contours but findContours returned 6!
I used threshold and problem solved:
Mat src=imread("E:\\test.bmp"),gry,bin,nor,dil,erd;
GaussianBlur( src, nor, Size(5,5),0 );
cvtColor(nor,gry,CV_BGR2GRAY);
threshold(gry,bin,0,255,THRESH_BINARY+THRESH_OTSU);
vector<vector<Point>> conts;
vector<Vec4i> hier;
findContours(bin,conts,hier,CV_RETR_TREE,CV_CHAIN_APPROX_SIMPLE);
Now it returns 4 contours which the 1st one is the image boundary(contour with index 0) and can be easily skipped.
This how I would do it
1. Canny for edge detection
2. Use houghtransform to detect the edges.
3. Detect the two edges that do an angle of 90.

Resources