Silhouette extraction from binary image - c++

I am working with binary images from CASIA database and opencv in a C++ project. I am looking for a way of extracting only the silhouette(the bounding box containing the silhouette). The original images are 240x320 and my goal is to get only the silhouette in a new image (let’s say 100x50 size).
My first idea would be to get the minimum and maximum position of “white” pixels on rows and columns and get the pixels inside this rectangle in a new image, but I consider this not efficient at all. If you have any suggetion, I would be more than happy to hear it. On the left is the input and on the right is the output.

You can use the built-in OpenCV functionalities to find contours from your binary image:
e.g.
// using namespace cv;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( your_binary_mat, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE );
Note this will look for external contours (ignores inner contours which for the image above don't apply anyway) and retrieve a simplified approximation of the points.
Once you access the contour you can use either boundingRect() or minAreaRect() (wether you need the bounding box rotated or not).

Related

OpenCV : Reversing the negative areas of an image

i'm using OpenCV 3.4.6 in a c++/Objective C project and given an image with negative rectangular areas, like this one:
I should detect those negative areas, reverse them and finally get the original image.
I tried to use findContours, enhancing the contrast of the original image or adding a threshold but the rectangles are not detected.
Here one of the test i've tried:
Mat contrasted = [self enhanceContrastTo: matOriginal];
Mat thresholded;
threshold(contrasted, thresholded, 125, 241, THRESH_BINARY);
std::vector<std::vector<cv::Point> > contours;
std::vector<Vec4i> hierarchy;
findContours( thresholded, contours, hierarchy, CV_RETR_EXTERNAL, CV_RETR_TREE );
/* contrast method */
+(Mat)enhanceContrastTo:(Mat)image {
cv::Mat lab_image;
cv::cvtColor(image, lab_image, CV_BGR2Lab);
// Extract the L channel
std::vector<cv::Mat> lab_planes(3);
cv::split(lab_image, lab_planes); // now we have the L image in lab_planes[0]
// apply the CLAHE algorithm to the L channel
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
// clahe->setClipLimit(4);
clahe->setClipLimit(3);
cv::Mat dst;
clahe->apply(lab_planes[0], dst);
// Merge the the color planes back into an Lab image
dst.copyTo(lab_planes[0]);
cv::merge(lab_planes, lab_image);
// convert back to RGB
cv::Mat image_clahe;
cv::cvtColor(lab_image, image_clahe, CV_Lab2BGR);
return image_clahe;
}
The rectangles are clearly visible to the naked eye, I hope that opencv can also identify them but I don't know how.
Any idea?
Thanks
This particular question isn't too complicated but even minor variants can get complex. I can advise you on a couple of simple ideas that should suffice to solve the problem.
1) Instead of contour you can check whether neighboring points are close to reverse - this should filter out most irrelevant edges. But just checking for near-reverse is not sufficient as monotone grey area (127) fits the criteria too. Require also minimal threshold difference.
2) Since rectangles are parallel to axes - you can simply go along each row and column and count the number of pixels that are potentially edges of the reversed rectangles. It is better not to just count the number - but to check whether you have continuous large sequences of such pixels and record where exactly these segments are.
3) Use the found segments (or just indexes of rows and columns) of reversed edge-pixels to make candidates for reversed rectangles and then make final verifications.
This is but an algo draft - it will surely require refining. I am not sure why you wanted to use the contour function, tho.

Finding connected components using OpenCV

I am trying to find and separate all edges in an edge detected image using python OpenCV. The edges can be in a form of contour but they don't have to. I just want all connected edges pixels to be grouped together. So technically the algorithm may procedurally sound like this:
For each edge pixel, find a neighbouring (connected) edge pixel and add it to a current subdivision of the image, until you can't find one anymore.
Then move on to the next unchecked edge pixel and start a new subdivision and do 1) again.
I have looked through cv.findContours but the results wasn't satisfying, maybe because it was intended for contours (enclosed edges) rather than free-ended ones. Here are the results:
Original Edge Detected:
After Contour Processing:
I expected the five edges would each be grouped into its own subdivision of the image, but apparently the cv2.findContours function breaks 2 of the edges even further into subdivisions which I don't want.
Here is the code I used to save these 2 images:
def contourForming(imgData):
cv2.imshow('Edge', imgData)
cv2.imwrite('EdgeOriginal.png', imgData)
contours = cv2.findContours(imgData, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.imshow('Contours', imgData)
cv2.imwrite('AfterFindContour.png', imgData)
cv2.waitKey(0)
pass
There are restrictions to my implementation, however. I have to use Python 2.7 and OpenCV2. I cannot use any other revision or languages besides these. I say this because I know OpenCV 2 has a connectedComponent function using C++. I could have used that but the problem is, I cannot use it due to certain limitations.
So, any idea how I should approach the problem?
Using findContours is the correct approach, you're simply doing it wrong.
Take a closer look to the documentation:
Note: Source image is modified by this function.
Your "After Contour Processing" image is in fact the garbage result from findContours. Because of this, if you want the original image to be intact after the call to findContours, it's common practice to pass a cloned image to the function.
The meaningful result of findContours is in contours. You need to draw them using drawContours, usually on a new image.
This is the result I get:
with the following C++ code:
#include <opencv2/opencv.hpp>
using namespace cv;
int main(int argc, char** argv)
{
// Load the grayscale image
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Prepare the result image, 3 channel, same size as img, all black
Mat3b res(img.rows, img.cols, Vec3b(0,0,0));
// Call findContours
vector<vector<Point>> contours;
findContours(img.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
// Draw each contour with a random color
for (int i = 0; i < contours.size(); ++i)
{
drawContours(res, contours, i, Scalar(rand() & 255, rand() & 255, rand() & 255));
}
// Show results
imshow("Result", res);
waitKey();
return 0;
}
It should be fairly easy to port to Python (I'm sorry but I can't give you Python code, since I cannot test it). You can also have a look at the specific OpenCV - Python tutorial to check how to correctly use findContours and drawContours.

Can I get a contour of the opencv grabcut function?

I am using opencv grabcut function for image segmentation. I have looked at the sample given in opencv of the grabcut usage, the sample is simply returning as an image where all the "background" parts are colored as black (0,0,0) . I can simply flood from every black point and get the contour. But I would like to use given function if they exist.
Grabcut returns a mask. You can use this code to get the contours:
std::vector<std::vector<cv::Point> > contours;
cv::findContours(mask, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

Finding contours after canny edge detection

I want to extract contours from a binary canny edge image.
The original image is:
After applying cvCanny() and cvDilate(), I get the following image:
I need the enclosing box(the entire blue box) to be detected as a contour. I apply cvFindContours() and extract the contour with the largest area. However, when I apply cvFindContours(), it modifies the above canny image as follows:
which is not what I intend to do. It then outputs the largest contour to be the mailbox sign inside the blue box.
What is going wrong? Does cvFindContours() modify the input image? What should be done to get just the enclosing blue box?
Thanks.
Yes, findContours indeed changes the images. If you still need your original image, than use findContours on copy of your image.
Instead of:
findContours(image, contours, mode, method);
Use:
findContours(image.clone(), contours, mode, method);
*Edit (answer to comment): *
It depends on what you define as "largest". If you use area this may be problematic because calling findContours on edge map may result in very long but very thin contours. Better definition of "largest" is contour whose bounding rectangle has biggest area. You can use function called boundingRect to find it. And if you want to find bounding box of all polygons use OR operator between all bounding boxes:
Rect bbox = boundingRect(contours[0]);
for(i=1; i<contours.size(); i++)
bbox = bbox | boundingRect(contours[i]);

Contours opencv : How to eliminate small contours in a binary image

I am currently working on image processing project. I am using Opencv2.3.1 with VC++.
I have written the code such that, the input image is filtered to only blue color and converted to a binary image. The binary image has some small objects which I don't want. I wanted to eliminate those small objects, so i used openCV's cvFindContours() method to detect contours in Binary image. but the problem is I cant eliminate the small objects in the image output. I used cvContourArea() function , but didn't work properly.. , erode function also didn't work properly.
So please someone help me with this problem..
The binary image which I obtained :
The result/output image which I want to obtain :
Ok, I believe your problem could be solved with the bounding box demo recently introduced by OpenCV.
As you have probably noticed, the object you are interested at should be inside the largest rectangle draw in the picture. Luckily, this code is not very complex and I'm sure you can figure it all out by investigating and experimenting with it.
Here is my solution to eliminate small contours.
The basic idea is check the length/area for each contour, then delete the smaller one from vector container.
normally you will get contours like this
Mat canny_output; //example from OpenCV Tutorial
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
Canny(src_img, canny_output, thresh, thresh*2, 3);//with or without, explained later.
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0,0));
With Canny() pre-processing, you will get contour segments, however each segment is stored with boundary pixels as a closed ring. In this case, you can check the length and delete the small one like
for (vector<vector<Point> >::iterator it = contours.begin(); it!=contours.end(); )
{
if (it->size()<contour_length_threshold)
it=contours.erase(it);
else
++it;
}
Without Canny() preprocessing, you will get contours of objects.
Similarity, you can also use area to define a threshold to eliminate small objects, as OpenCV tutorial shown
vector<Point> contour = contours[i];
double area0 = contourArea(contour);
this contourArea() is the number of non-zero pixels
Are you sure filtering by small contour area didn't work? It's always worked for me. Can we see your code?
Also, as sue-ling mentioned, it's a good idea to use both erode and dilate to approximately preserve area. To remove small noisy bits, use erode first, and to fill in holes, use dilate first.
And another aside, you may want to check out the new C++ versions of the cv* functions if you weren't aware of them already (documentation for findContours). They're much easier to use, in my opinion.
Judging by the before and after images, you need to determine the area of all the white areas or blobs, then apply a threshold area value. This would eliminate all areas less than the value and leave only the large white region which is seen in the 2nd image. After using the cvFindContours function, try using 0 order moments. This would return the area of the blobs in the image. This link might be helpful in implementing what I've just described.
http://www.aishack.in/2010/07/tracking-colored-objects-in-opencv/
I believe you can use morphological operators like erode and dilate (read more here)
You need to perform erosion with a kernel size near to the radius of the circle on the right (the one you want to eliminate).
followed by dilation using the same kernel to fill the gaps created by the erosion step.
FYI erosion followed by dilation using the same kernel is called opening.
the code will be something like this
int erosion_size = 30; // adjust with you application
Mat erode_element = getStructuringElement( MORPH_ELLIPSE,
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
Point( erosion_size, erosion_size ) );
erode( binary_img, binary_img, erode_element );
dilate( binary_img, binary_img, erode_element );
It is not a fast way but may be usefull in some cases.
There is a new function in OpencCV 3.0 - connectedComponentsWithStats. With it we can get area of connected components and eliminate unnecessary. So we can easy remove circle with holes, with the same bounding box as solid circle.