Mismatch in number of detected contours in OpenCV - image-processing

As per the documentation, the function cvFindContours() returns the number of retrieved contours from the binary image input. However when I'm running a loop over all the contours the number of detected contours is drastically less.
A possible reason would be that child contours of a parent, as well as holes are being counted in the return value of the function. Even so this number is not matching a reasonable eye-estimate of the pictures used by me.
The return value in this case is 92, whereas in traversing all the contours there are 15 distinct contours.
Here's the code:
int n = cvFindContours(img_canny_mop, mem, &contours, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
printf("No of retrieved contours = %d",n);
for (ptr = contours; ptr != NULL; ptr = ptr->h_next)
{
ext_color = CV_RGB( rand()&255, rand()&255, rand()&255 ); //randomly coloring different contours
cvDrawContours(cc_color, ptr, ext_color, CV_RGB(0,0,0), -1, CV_FILLED, 8, cvPoint(0,0));
//display( cc_color, "CCs");
n_cont++;
}
cvSaveImage("CC_colors.jpg",cc_color);
printf("\nNo of contours = %d",n_cont);
The images are:
Input: http://imageshack.us/photo/my-images/811/cannymop.jpg/
Randomly colored contours: http://imageshack.us/photo/my-images/819/cccolors.jpg/

You may want to look at the documentation here. The key part being the following.
The pointer firstContour is filled by
the function. It will contain pointer
to the first most outer contour or
NULL if no contours is detected (if
the image is completely black). Other
contours may be reached from
firstContour using h_next and v_next
links.
May be you need to check the number of v_next for each h_next (or vice versa).

Related

find contours finds too many contours on simulated image

I want to find the contours of a binary image of segmented rocks. There are some problems with the findContours function from opencv.
The contour size is around 1000 while the contours from the binary image could be around 30-50.
When I draw ALL the contours, they seem to be a decent representation of the black boundaries from the binary image. But When I draw only one contour of some random index, it shows a small contour.
Images are given below :
Binary Image
Contours of all the index
Contour of a random contour index. The small green contour
I would like to have just the exact number of contours as in the binary image.
Code :
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(input_image, contours,hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
for( int i = 0; i < (int)contours.size(); i++)
{
cv::drawContours(input_rgb_image, contours, 512 , cv::Scalar(0,255,0), 1, 8, hierarchy,1);
}
There are two problems with your code. You will get better results if you invert and blur the image. These are my results after applying those two operations before finding the contours:
The OpenCV findContours() function finds dark contours on the light background. If you want to find the white spaces, which are the rocks, you need to invert the binary image first. You can invert a binary image like this invertedImage = 255 - binaryImage. Blurring also helps because it connects pixels that should be connected but aren't because of the low resolution. Blurring is done with the code blurredImage = cv2.blur(img, (2,2)). This is the inverted blurred image:
This is the code that I used:
import cv2
import random
# Read image
gray = 255-cv2.imread('/home/stephen/Desktop/image.png', 0)
gray = cv2.blur(gray, (2,2))
# Find contours in image
contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))
img = cv2.imread('/home/stephen/Desktop/image.png')
for cnt in contours:
color = random.randint(0,255),random.randint(0,255),random.randint(0,255)
img = cv2.drawContours(img, [cnt], 0, color, cv2.FILLED)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I would try a couple of things:
bilateral filter instead of blur. It smoothes things in a way similar
to blur but also tries to preserve boundaries, which is good for segmentation. Downsides - it's computationally expensive but you may
find "your" params that play well for free
blur + meanshift segmentation before the watershed. Blur will act just
like expected and meanshift will average and join contours with
similar colors and as such make the number of contours smaller.
Depending on params, meanshift is also expensive. Just play with
it.
More advanced thing is contours analysis afterward. You could unite some of the neighbors based on:
the similarity of the histogram on some of hsv channels;
contours properties, such as roundness. If roundness of two united
neighbors is better than the roundness of any of them then they can be united. Something like this.
Roundness calculating:
float calcRoundness(std::vector<cv::Point> &contour, double area)
{
float p = cv::arcLength(contour, true);
if (p == 0)
return 0;
float k = (4 * M_PI * area) / pow(p, 2);
/* 1 is circle, 0.75 - squared area, etc. */
return k;
}

Counting erythrocytes

I'm trying to count the number of erythrocytes on a microscope image. These are the smaller cells. (I've tried first using CNN and sliding window, but it was too slow, so I'm looking for a simplier segmentation)
My approach is:
threshold
find and draw all contours filled so that the cells won't have holes,
make distance transform
iterating over all maxima
masking out a current maximum with a circle having the radius of the maximum and storing the maximum position
My problem is, some cells have a "hole" in the middle - bright area similar by the value to background. If I threshold the image, some of the cell-masks become not a circle but a half circle, with the distance-transform values far below expected value.
I've marked the cells having the "holes" on the mask image.
Hov could I close the hole or the circle? Is there a threshold method or trick?
Below is the part of code responsible for cell extraction:
cv::adaptiveThreshold(_imgIn ,th, 255, ADAPTIVE_THRESH_GAUSSIAN_C, (bgblack ? CV_THRESH_BINARY: CV_THRESH_BINARY_INV), 35, 5 );//| CV_THRESH_OTSU);
Mat kernel1 = Mat::ones(3, 3, CV_8UC1);
for (int i=0; i< 5;i++)
{
dilate(th, th, kernel1);
erode(th, th, kernel1);
}
vector<vector<Point> > contours;
findContours(th, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
mask = 0;
for( unsigned int i = 0; i < contours.size(); i++ )
{
drawContours(mask, contours, i, Scalar(255), CV_FILLED);
}
cv::distanceTransform(mask, dist, CV_DIST_L2, 3);
}
double min, max;
cv::Point pmax;
Mat tmp1 = dist.clone();
while (true)
{
cv::minMaxLoc(tmp1, 0, &max, 0, &pmax);
if ( max < 5 )
break;
cv::circle(_imgIn, pmax, 3 , cv::Scalar(0), CV_FILLED );
cv::circle(tmp1, pmax, max , cv::Scalar(0), CV_FILLED );
}
Closing holes
Closing is an important operator from the field of mathematical morphology. Like its dual operator opening, it can be derived from the fundamental operations of erosion and dilation. Like those operators it is normally applied to binary images, although there are graylevel versions. Closing is similar in some ways to dilation in that it tends to enlarge the boundaries of foreground (bright) regions in an image (and shrink background color holes in such regions), but it is less destructive of the original boundary shape. As with other morphological operators, the exact operation is determined by a structuring element. The effect of the operator is to preserve background regions that have a similar shape to this structuring element, or that can completely contain the structuring element, while eliminating all other regions of background pixels.
In Open CV this looks as follows
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations = 1)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
Full documentation here.

Comparing white pixels of two contours in opencv

I have to calculate white pixels and compare two contours in opencv that is one contour in first and the fifth frame then fifth and the tenth frame and so on.
I searched a lot about how to find next contour in a video. but all in vain. I am in doubt whether opencv has the function to find next contour. I am completely confused by reading the tutorials and other things.
I have done this. I doubt my logic.
cvFindContours(bgModel->foreground, memory, &contour, sizeof(CvContour),CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
if(FrameNumber%5==0)
{
for( ; contour != 0; contour = contour->h_next )
{
double area = fabs(cvContourArea(contour,CV_WHOLE_SEQ, 0));
I dont know what to do after this. how to get next contour????
Here is a code fragment iterating contours your counour is in 'c' variable.
//Contour Stuffz
CvMemStorage* g_storage = NULL;
g_storage = cvCreateMemStorage(0);
CvSeq* contours = 0;
cvFindContours(r2, g_storage, &contours);
for (CvSeq* c = contours; c != NULL; c=c->h_next) {
//*** Contour Stuff
int nContourPoints = c->total; // Get total number of points in the chain
if (nContourPoints > 46) { ...

Error using cvCountNonZero in opencv

I am trying to count the number of non-zero pixels in a contour retrieved from a Canny edged image using openCV (using C). I am using cvFindNextContour to find the subsequent contour retrieved using a contour scanner.
But When I use the cvCountNonZero on the contour, an error shows up:
Bad flag (parameter or structure field) (Unrecognized or unsupported array type)
in function cvGetMat, C:\User\..\cvarray.cpp(2881)
My code is:
cvCvtColor(image, gray, CV_BGR2GRAY);
cvCanny(gray, edge, (float)edge_thresh, (float)edge_thresh*4, 3);
sc = cvStartFindContours( edge, mem,
sizeof(CvContour),
CV_RETR_LIST,
CV_CHAIN_APPROX_SIMPLE,
cvPoint(0,0) );
while((contour = cvFindNextContour(sc))!=NULL)
{
CvScalar color = CV_RGB( rand()&255, rand()&255, rand()&255 );
printf("%d\n",cvCountNonZero(contour));
cvDrawContours(final, contour, color, CV_RGB(0,0,0), -1, 1, 8, cvPoint(0,0));
}
Any kind of help is highly appreciated. Thanks in advance.
cvCountNonZero(CvArr*) is for finding the number of non zeros in an array or IplImage but not for CvSeq* contour type. That is why the error is coming. Here teh solution to the problem.
CvRect rect = cvBoundingRect( contour, 0);
cvSetImageROI(img1,rect);
cout<<cvCountNonZero(img1)<<endl;
cvResetImageROI(img1);
//where img1 is the binary image in which you find the contours.
The code can be explained in the following way:
1.First make a rectangular region around each contour.
2.Set the image ROI to that particular region.
3.Now use the cvCountNonZero(); function to find the number of non zeros in the Region.
4.Reset the image ROI.
Have a happy coding.

Filling holes inside a binary object

I have a problem with filling white holes inside a black coin so that I can have only 0-255 binary images with filled black coins. I have used a Median filter to accomplish it but in that case connection bridge between coins grows and it goes impossible to recognize them after several times of erosion... So I need a simple floodFill like method in opencv
Here is my image with holes:
EDIT: floodfill like function must fill holes in big components without prompting X, Y coordinates as a seed...
EDIT: I tried to use the cvDrawContours function but it doesn't fill contours inside bigger ones.
Here is my code:
CvMemStorage mem = cvCreateMemStorage(0);
CvSeq contours = new CvSeq();
CvSeq ptr = new CvSeq();
int sizeofCvContour = Loader.sizeof(CvContour.class);
cvThreshold(gray, gray, 150, 255, CV_THRESH_BINARY_INV);
int numOfContours = cvFindContours(gray, mem, contours, sizeofCvContour, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
System.out.println("The num of contours: "+numOfContours); //prints 87, ok
Random rand = new Random();
for (ptr = contours; ptr != null; ptr = ptr.h_next()) {
Color randomColor = new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat());
CvScalar color = CV_RGB( randomColor.getRed(), randomColor.getGreen(), randomColor.getBlue());
cvDrawContours(gray, ptr, color, color, -1, CV_FILLED, 8);
}
CanvasFrame canvas6 = new CanvasFrame("drawContours");
canvas6.showImage(gray);
Result: (you can see black holes inside each coin)
There are two methods to do this:
1) Contour Filling:
First, invert the image, find contours in the image, fill it with black and invert back.
des = cv2.bitwise_not(gray)
contour,hier = cv2.findContours(des,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
cv2.drawContours(des,[cnt],0,255,-1)
gray = cv2.bitwise_not(des)
Resulting image:
2) Image Opening:
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
res = cv2.morphologyEx(gray,cv2.MORPH_OPEN,kernel)
The resulting image is as follows:
You can see, there is not much difference in both cases.
NB: gray - grayscale image, All codes are in OpenCV-Python
Reference. OpenCV Morphological Transformations
A simple dilate and erode would close the gaps fairly well, I imagine. I think maybe this is what you're looking for.
A more robust solution would be to do an edge detect on the whole image, and then a hough transform for circles. A quick google shows there are code samples available in various languages for size invariant detection of circles using a hough transform, so hopefully that will give you something to go on.
The benefit of using the hough transform is that the algorithm will actually give you an estimate of the size and location of every circle, so you can rebuild an ideal image based on that model. It should also be very robust to overlap, especially considering the quality of the input image here (i.e. less worry about false positives, so can lower the threshold for results).
You might be looking for the Fillhole transformation, an application of morphological image reconstruction.
This transformation will fill the holes in your coins, even though at the cost of also filling all holes between groups of adjacent coins. The Hough space or opening-based solutions suggested by the other posters will probably give you better high-level recognition results.
In case someone is looking for the cpp implementation -
std::vector<std::vector<cv::Point> > contours_vector;
cv::findContours(input_image, contours_vector, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
cv::Mat contourImage(input_image.size(), CV_8UC1, cv::Scalar(0));
for ( ushort contour_index = 0; contour_index < contours_vector.size(); contour_index++) {
cv::drawContours(contourImage, contours_vector, contour_index, cv::Scalar(255), -1);
}
cv::imshow("con", contourImage);
cv::waitKey(0);
Try using cvFindContours() function. You can use it to find connected components. With the right parameters this function returns a list with the contours of each connected components.
Find the contours which represent a hole. Then use cvDrawContours() to fill up the selected contour by the foreground color thereby closing the holes.
I think if the objects are touched or crowded, there will be some problems using the contours and the math morophology opening.
Instead, the following simple solution is found and tested. It is working very well, and not only for this images, but also for any other images.
here is the steps (optimized) as seen in http://blogs.mathworks.com/steve/2008/08/05/filling-small-holes/
let I: the input image
1. filled_I = floodfill(I). // fill every hole in the image.
2. inverted_I = invert(I)`.
3. holes_I = filled_I AND inverted_I. // finds all holes
4. cc_list = connectedcomponent(holes_I) // list of all connected component in holes_I.
5. holes_I = remove(cc_list,holes_I, smallholes_threshold_size) // remove all holes from holes_I having size > smallholes_threshold_size.
6. out_I = I OR holes_I. // fill only the small holes.
In short, the algorithm is just to find all holes, remove the big ones then write the small ones only on the original image.
I've been looking around the internet to find a proper imfill function (as the one in Matlab) but working in C with OpenCV. After some reaserches, I finally came up with a solution :
IplImage* imfill(IplImage* src)
{
CvScalar white = CV_RGB( 255, 255, 255 );
IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3);
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contour = 0;
cvFindContours(src, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
cvZero( dst );
for( ; contour != 0; contour = contour->h_next )
{
cvDrawContours( dst, contour, white, white, 0, CV_FILLED);
}
IplImage* bin_imgFilled = cvCreateImage(cvGetSize(src), 8, 1);
cvInRangeS(dst, white, white, bin_imgFilled);
return bin_imgFilled;
}
For this: Original Binary Image
Result is: Final Binary Image
The trick is in the parameters setting of the cvDrawContours function:
cvDrawContours( dst, contour, white, white, 0, CV_FILLED);
dst = destination image
contour = pointer to the first contour
white = color used to fill the contour
0 = Maximal level for drawn contours. If 0, only contour is drawn
CV_FILLED = Thickness of lines the contours are drawn with. If it is negative (For example, =CV_FILLED), the contour interiors are drawn.
More info in the openCV documentation.
There is probably a way to get "dst" directly as a binary image but I couldn't find how to use the cvDrawContours function with binary values.

Resources