Detect triangles an rectangles from image with opencv - c++

I have this image:
And i want take out the triangle and rectangles from the image. I have 2 algorithmes, one for triangles and another for rectangles in the code below. But they are very similar. But in this way i only can take out the triangle more bright. Can anyone help me please.
IplImage* DetectAndDrawTriang(IplImage* img){
CvSeq* contours;
CvSeq* result;
CvMemStorage *storage = cvCreateMemStorage(0);
int d=30;
IplImage* ret = cvCreateImage(cvGetSize(img), 8, 3);
IplImage* temp = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
cvSet(ret,cvScalar(0,0,0));
cvCvtColor(img, temp, CV_BGR2GRAY);
cvThreshold( temp, temp, 180, 255, CV_THRESH_BINARY );
//cvSmooth(temp, temp, CV_GAUSSIAN, 9, 9, 0,0);
cvNamedWindow("thre");
cvShowImage("thre", temp);
cvFindContours(temp, storage, &contours, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
while(contours)
{
result = cvApproxPoly(contours, sizeof(CvContour), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.1, 0);
if(result->total==3)
{
CvPoint *pt[3];
for(int i=0;i<3;i++)
pt[i] = (CvPoint*)cvGetSeqElem(result, i);
if((int)sqrt((pt[0]->x-pt[2]->x)*(pt[0]->x-pt[2]->x)+(pt[0]->y-pt[2]->y)*(pt[0]->y-pt[2]->y))>=d && (int)sqrt((pt[0]->x-pt[1]->x)*(pt[0]->x-pt[1]->x)+(pt[0]->y-pt[1]->y)*(pt[0]->y-pt[1]->y))>=d && (int)sqrt((pt[1]->x-pt[2]->x)*(pt[1]->x-pt[2]->x)+(pt[1]->y-pt[2]->y)*(pt[1]->y-pt[2]->y))>=d)
{
cvLine(ret, *pt[0], *pt[1], cvScalar(255,255,255));
cvLine(ret, *pt[1], *pt[2], cvScalar(255,255,255));
cvLine(ret, *pt[2], *pt[0], cvScalar(255,255,255));
}
}
contours = contours->h_next;
}
cvReleaseImage(&temp);
cvReleaseMemStorage(&storage);
return ret;
}

One Idea I can think of is using the cv::matchShapes function (I suggest using the cv2 library with Mat instead of Ipl images). matchShapes takes a Mat of the object you want to detect and a Mat of the object you want to compare it against. So in your case you can make a Mat of the contours of a triangle and square and compare those images with each contour in the image your are searching through.
You may also consider simply doing template matching since your objects are static. Check out cv::matchTemplate and its pretty much the same idea as the above paragraph.

ApproxPoly is a good solution if you can be sure that your contours are complete.
If the contour is a square but doesn't close then it will be a line with four segments and 3 corners after approximating.
Another solution is to fit a box around the contour points (there is a function to do this) and check the width/height ratio. You can then test the individual line segments in the contour list to see if they match the box sides.

Related

How to straighten curved line using OpenCV?

I have image with curved line like this :
I couldn't find a technique to straighten curved line using OpenCV. It is similar to this post Straightening a curved contour, but my question is specific to coding using opencv (in C++ is better).
So far, I'm only able to find the contour of the curved line.
int main()
{
Mat src; Mat src_gray;
src = imread("D:/2.jpg");
cvtColor(src, src_gray, COLOR_BGR2GRAY);
cv::blur(src_gray, src_gray, Size(1, 15));
Canny(src_gray, src_gray, 100, 200, 3);
/// Find contours
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
RNG rng(12345);
findContours(src_gray, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
/// Draw contours
Mat drawing = Mat::zeros(src_gray.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++)
{
drawContours(drawing, contours, i, (255), 1, 8, hierarchy, 0, Point());
}
imshow("Result window", drawing);
imwrite("D:/C_Backup_Folder/Ivan_codes/VideoStitcher/result/2_res.jpg", drawing);
cv::waitKey();
return 0;
}
But I have no idea how to determine which line is curved and not, and how to straighten it. Is it possible? Any help would be appreciated.
Here is my suggestion:
Before everything, resize your image into a much bigger image (for example 5 times bigger). Then do what you did before, and get the contours. Find the right-most pixel of each contour, and then survey all pixel of that contour and count the horizontal distance of each pixels to the right-most pixel and make a shift for that row (entire row). This method makes a right shift to some rows and left shift to the others.
If you have multiple contours, calculate this shift value for every one of them in every single row and compute their "mean" value, and do the shift according to that mean value for each row.
At the end resize back your image. This is the simplest and fastest thing I could think of.

OpenCV: how can I interpret the results of inRange?

I am processing video images and I would like to detect if the video contains any pixels of a certain range of red. Is this possible?
Here is the code I am adapting from a tutorial:
#ifdef __cplusplus
- (void)processImage:(Mat&)image;
{
cv::Mat orig_image = image.clone();
cv::medianBlur(image, image, 3);
cv::Mat hsv_image;
cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV);
cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);
// Interpret values here
}
Interpreting values
I would like to detect if the results from the inRange operations are nil or not. In other words I want to understand if there are any matching pixels in the original image with a colour inRange from the given lower and upper red scale. How can I interpret the results?
First you need to OR the lower and upper mask:
Mat mask = lower_red_hue_range | upper_red_hue_range;
Then you can countNonZero to see if there are non zero pixels (i.e. you found something).
int number_of_non_zero_pixels = countNonZero(mask);
It could be better to first apply morphological erosion or opening to remove small (probably noisy) blobs:
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(mask, mask, MORPH_OPEN, kernel); // or MORPH_ERODE
or find connected components (findContours, connectedComponentsWithStats) and prune / search for according to some criteria:
vector<vector<Point>> contours
findContours(mask.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
double threshold_on_area = 100.0;
for(int i=0; i<contours.size(); ++i)
{
double area = countourArea(contours[i]);
if(area < threshold_on_area)
{
// don't consider this contour
continue;
}
else
{
// do something (e.g. drawing a bounding box around the contour)
Rect box = boundingRect(contours[i]);
rectangle(hsv_image, box, Scalar(0, 255, 255));
}
}

OpenCV: Is it possible to detect rectangle from corners?

I have a photo where a person holds a sheet of paper. I'd like to detect the rectangle of that sheet of paper.
I have tried following different tutorials from OpenCV and various SO answers and sample code for detecting squares / rectangles, but the problem is that they all rely on contours of some kind.
If I follow the squares.cpp example, I get the following results from contours:
As you can see, the fingers are part of the contour, so the algorithm does not find the square.
I, also, tried using HoughLines() approach, but I get similar results to above:
I can detect the corners, reliably though:
There are other corners in the image, but I'm limiting total corners found to < 50 and the corners for the sheet of paper are always found.
Is there some algorithm for finding a rectangle from multiple corners in an image? I can't seem to find an existing approach.
You can apply a morphological filter to close the gaps in your edge image. Then if you find the contours, you can detect an inner closed contour as shown below. Then find the convexhull of this contour to get the rectangle.
Closed edges:
Contour:
Convexhull:
In the code below I've just used an arbitrary kernel size for morphological filter and filtered out the contour of interest using an area ratio threshold. You can use your own criteria instead of those.
Code
Mat im = imread("Sh1Vp.png", 0); // the edge image
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(11, 11));
Mat morph;
morphologyEx(im, morph, CV_MOP_CLOSE, kernel);
int rectIdx = 0;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(morph, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for (size_t idx = 0; idx < contours.size(); idx++)
{
RotatedRect rect = minAreaRect(contours[idx]);
double areaRatio = abs(contourArea(contours[idx])) / (rect.size.width * rect.size.height);
if (areaRatio > .95)
{
rectIdx = idx;
break;
}
}
// get the convexhull of the contour
vector<Point> hull;
convexHull(contours[rectIdx], hull, false, true);
// visualization
Mat rgb;
cvtColor(im, rgb, CV_GRAY2BGR);
drawContours(rgb, contours, rectIdx, Scalar(0, 0, 255), 2);
for(size_t i = 0; i < hull.size(); i++)
{
line(rgb, hull[i], hull[(i + 1)%hull.size()], Scalar(0, 255, 0), 2);
}

Find Color inside an shape C++ OpenCV

I have a code that find the countours of a video and track the shape that I choose. In this code I am searching for triangle and rectangle looking at 3 or 4 contours.
I need help in 2 questions:
1- Using this method, how can I detect circles?
2- How can I search the color ( the shape is done, so in my "if" I need to verify the color too, but how ? If I for example want to find a red triangle )
Thank you so much
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp""
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv\cv.h>
#include <math.h>
using namespace cv;
using namespace std;
int iLastX = -1;
int iLastY = -1;
IplImage* imgTracking = 0;
int lastX1 = -1;
int lastY1 = -1;
int lastX2 = -1;
int lastY2 = -1;
void trackObject(IplImage* imgThresh){
CvSeq* contour; //hold the pointer to a contour
CvSeq* result; //hold sequence of points of a contour
CvMemStorage *storage = cvCreateMemStorage(0); //storage area for all contours
//finding all contours in the image
cvFindContours(imgThresh, storage, &contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
//iterating through each contour
while (contour)
{
//obtain a sequence of points of the countour, pointed by the variable 'countour'
result = cvApproxPoly(contour, sizeof(CvContour), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contour)*0.02, 0);
//if there are 3 vertices in the contour and the area of the triangle is more than 100 pixels
if (result->total == 3 && fabs(cvContourArea(result, CV_WHOLE_SEQ))>100)
{
//iterating through each point
CvPoint *pt[3];
for (int i = 0; i < 3; i++){
pt[i] = (CvPoint*)cvGetSeqElem(result, i);
}
//drawing lines around the triangle
cvLine(imgTracking, *pt[0], *pt[1], cvScalar(255, 0, 0), 4);
cvLine(imgTracking, *pt[1], *pt[2], cvScalar(255, 0, 0), 4);
cvLine(imgTracking, *pt[2], *pt[0], cvScalar(255, 0, 0), 4);
}
else if (result->total == 4 && fabs(cvContourArea(result, CV_WHOLE_SEQ))>100)
{
//iterating through each point
CvPoint *pt[4];
for (int i = 0; i < 4; i++){
pt[i] = (CvPoint*)cvGetSeqElem(result, i);
}
//drawing lines around the quadrilateral
cvLine(imgTracking, *pt[0], *pt[1], cvScalar(0, 255, 0), 4);
cvLine(imgTracking, *pt[1], *pt[2], cvScalar(0, 255, 0), 4);
cvLine(imgTracking, *pt[2], *pt[3], cvScalar(0, 255, 0), 4);
cvLine(imgTracking, *pt[3], *pt[0], cvScalar(0, 255, 0), 4);
}
else if (CIRCLE???)
{
}
//obtain the next contour
contour = contour->h_next;
}
cvReleaseMemStorage(&storage);
}
int main(){
//load the video file to the memory
CvCapture *capture = cvCaptureFromAVI("F:/TCC/b2.avi");
if (!capture){
printf("Capture failure\n");
return -1;
}
IplImage* frame = 0;
frame = cvQueryFrame(capture);
if (!frame) return -1;
//create a blank image and assigned to 'imgTracking' which has the same size of original video
imgTracking = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3);
cvZero(imgTracking); //covert the image, 'imgTracking' to black
cvNamedWindow("Video");
//iterate through each frames of the video
while (true){
cvSet(imgTracking, cvScalar(0, 0, 0));
frame = cvQueryFrame(capture);
if (!frame) break;
frame = cvCloneImage(frame);
//smooth the original image using Gaussian kernel
cvSmooth(frame, frame, CV_GAUSSIAN, 3, 3);
//converting the original image into grayscale
IplImage* imgGrayScale = cvCreateImage(cvGetSize(frame), 8, 1);
cvCvtColor(frame, imgGrayScale, CV_BGR2GRAY);
//thresholding the grayscale image to get better results
cvThreshold(imgGrayScale, imgGrayScale, 100, 255, CV_THRESH_BINARY_INV);
//track the possition of the ball
trackObject(imgGrayScale);
// Add the tracking image and the frame
cvAdd(frame, imgTracking, frame);
cvShowImage("Video", frame);
//Clean up used images
cvReleaseImage(&imgGrayScale);
cvReleaseImage(&frame);
//Wait 10mS
int c = cvWaitKey(10);
//If 'ESC' is pressed, break the loop
if ((char)c == 27) break;
}
cvDestroyAllWindows();
cvReleaseImage(&imgTracking);
cvReleaseCapture(&capture);
return 0;
}
Afaik, this will be a relatively complex project, not answerable in a single question, but here is my 2 cents. I'll try to sketch the big painting that comes to my mind right now and just hope it helps you somehow.
Basically, the process can be decoupled in:
Thresholding the image to get promising pixels out. As you say, you need to specify the color, so you can use that to set the threshold, i.e.: if you're looking for red objects, you can use a threshold that has a high R channel level and low on others.
Once you have promising figures out, you need a labeling algorithm to separate all figures. You can use this OpenCV addon library for that. This way, you will have all figures you found separated.
Now comes the hardest part imho. You can use a classifier to classify figures (there are a lot of docs on OpenCV classifiers out there, like this one). When you use a classifier for this task, you're comparing an input shape (in this case each figure you labeled previously) with several class of shapes (triangles and circles in your case), and decide which class fits better your input shape. The comparation is what the classifier does for you. Having told that, you need two things:
Define what properties you'll get from the shapes to be able to compare them. You can use Hu moments for that.
A dataset extracted from a training set of figures that you already know which class are. This is, you basically will get a bunch of well-known triangles, circles, squares and whatever shapes come to your mind, and get the properties at step 3.1 out of them.
This way, to check what class a figure fits on, you'll feed the classifier with the trained dataset with properties of well known classes, and the properties (i.e.: Hu moments) of an input shape (you got those from labeling).
Note that when using a classifier, if you detect a shape that you didn't provide data for at the training dataset, the classifier will try to fit it the better it can with the classes you did provide. For example, if you made the trained dataset for triangles and squares, when you provide a circle shape, the classifier will tell you it's a square or a triangle.
Sorry for not providing details, but I'm sure you can get info on the net on how to use a classifier, and check the OpenCV docs. Maybe also come later with more specific issues, but afaik, this is the thing I can tell.

Search for contours within a contour / OpenCV c++

I am trying to track a custom circular marker in an image, and I need to check that a circle contains a minimum number of other circles/objects. My code for finding circles is below:
void findMarkerContours( int, void* )
{
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
vector<Point> approx;
cv::Mat dst = src.clone();
cv::Mat src_gray;
cv::cvtColor(src, src_gray, CV_BGR2GRAY);
//Reduce noise with a 3x3 kernel
blur( src_gray, src_gray, Size(3,3));
//Convert to binary using canny
cv::Mat bw;
cv::Canny(src_gray, bw, thresh, 3*thresh, 3);
imshow("bw", bw);
findContours(bw.clone(), contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
Mat drawing = Mat::zeros( bw.size(), CV_8UC3 );
for (int i = 0; i < contours.size(); i++)
{
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
// contour
drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
//Approximate the contour with accuracy proportional to contour perimeter
cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true) *0.02, true);
//Skip small or non-convex objects
if(fabs(cv::contourArea(contours[i])) < 100 || !cv::isContourConvex(approx))
continue;
if (approx.size() >= 8) //More than 6-8 vertices means its likely a circle
{
drawContours( dst, contours, i, Scalar(0,255,0), 2, 8);
}
imshow("Hopefully we should have circles! Yay!", dst);
}
namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
imshow( "Contours", drawing );
}
As you can see the code to detect circles works quite well:
But now I need to filter out markers that I do not want. My marker is the bottom one. So once I have found a contour that is a circle, I want to check if there are other circular contours that exist within the region of the first circle and finally check the color of the smallest circle.
What method can I take to say if (circle contains 3+ smaller circles || smallest circle is [color] ) -> do stuff?
Take a look at the documentation for
findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
You'll see that there's an optional hierarchy output vector which should be handy for your problem.
hierarchy – Optional output vector, containing information about the image topology. It has as many elements as the number of contours.
For each i-th contour contours[i] , the elements hierarchy[i][0] ,
hiearchyi , hiearchyi , and hiearchyi are set to
0-based indices in contours of the next and previous contours at the
same hierarchical level, the first child contour and the parent
contour, respectively. If for the contour i there are no next,
previous, parent, or nested contours, the corresponding elements of
hierarchy[i] will be negative.
When calling findCountours using CV_RETR_TREE you'll be getting the full hierarchy of each contour that was found.
This doc explains the hierarchy format pretty well.
You are already searching for circles of a certain size
//Skip small or non-convex objects
if(fabs(cv::contourArea(contours[i])) < 100 || !cv::isContourConvex(approx))
continue;
So you can use that to look for smaller circles than the one youve got, instead of looking for < 100 look for contours.size
I imagine there is the same for color also...