Related
I am trying to run the followin code (based on this page) on an image, but it doesn't work:
Mat src=imread("img.jpg",1);
Mat tmp,thr;
cvtColor(src,tmp,CV_BGR2GRAY);
threshold(tmp,thr,200,255,THRESH_BINARY_INV);
vector< vector <Point> > contours;
vector< Vec4i > hierarchy;
Mat dst(src.rows,src.cols,CV_8UC1,Scalar::all(0));//Ceate Mat to draw contour
int box_w=10; // Define box width here
int box_h=10; // Define box height here
int threshold_perc=25; //perceantage value for eliminating the box according to pixel count inside the box
int threshold=(box_w*box_h*threshold_perc)/100;
findContours( thr, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); //Find contour
for( int i = 0; i< contours.size(); i++ ){
drawContours( dst,contours, i, Scalar(255,255,255),CV_FILLED, 8, hierarchy ); // Draw contour with thickness = filled
Rect r= boundingRect(contours[i]); // Find bounding rect
// Scan the image with in bounding box
for(int j=r.x;j<r.x+r.width;j=j+box_w){
for(int k=r.y;k<r.y+r.height;k=k+box_h){
Rect roi_rect(j,k,box_w,box_h);
Mat roi = dst(roi_rect);
int count = countNonZero(roi);
if(count > threshold)
rectangle(src, roi_rect, Scalar(255,0,0),1,8,0 );
}
}
}
imshow("src",src);
waitKey();
It works fine for any normal image, but for the images below, it either breaks or doesn't find the contour and draws boxes all over the image.
It says:
Unhandled exception at 0x00007FF9A72DA388 in test2.exe: Microsoft C++ exception: cv::Exception at memory location 0x000000FECC9DEAC0.
It breaks and points to here:
inline
Mat Mat::operator()( const Rect& roi ) const
{
return Mat(*this, roi);
}
in mat.inl.hpp.
What is wrong with my image? I have changed it from Gray-scale to RGB, but didn't help.
On the following image, it works fine:
As I commented, you're trying to access a region of the image that doesn't exist by using a rectangle of fixed size.
By intersecting the roi with the rectangle, you can avoid this problem:
Mat roi = dst(roi_rect & r);
The problem was that in the first images, the contour gets close to the boundaries of the image and in the bottom for loop of the program, it exceeds the coordinates. It was fixed with this:
// Scan the image with in bounding box
for (int j = r.x;j<r.x + r.width;j = j + box_w) {
for (int k = r.y;k<r.y + r.height;k = k + box_h) {
Rect roi_rect(j, k, box_w, box_h);
if (j + box_w < dst.cols && k + box_h < dst.rows)
{
Mat roi = dst(roi_rect);
int count = countNonZero(roi);
if (count > threshold)
rectangle(src, roi_rect, Scalar(0,0,255), 1, 8, 0);
}
}
}
currently I have segmented the object (rectangular) and now I want to create a Line profile. I dont know how to get along this line.
detected object
Aim is to get this:
object with lines
Update 14:25:
I know already the angle from the bounding rect and used this to calculate the shift in y-direction in order to rearrange the values to a new mat so that I only need go through the matrix to get a line profile.
Here my Code, but the rearrangement did not work.
Mat imgIn(SizeY, SizeX, CV_16U, &Wire[0]),
imgOut(SizeY, SizeX, CV_16U, Scalar(0)),
temp, drawing, mask, lineProfile(SizeY, SizeX, CV_16U, Scalar(0));
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Point center;
char buffer[100];
bool found = false;
int rect_no (0);
double angle(0.0);
// Detecting outer contours
temp = ::adaptiveThreshold(imgIn, SizeY, SizeX, kernelSize, thresh, 0);
// Find contours
findContours(temp, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_TC89_KCOS, Point(0, 0) );
/// Find the rotated rectangles and ellipses for each contour
vector<RotatedRect> minRect( contours.size() );
for( int i = 0; i < contours.size(); i++ ) minRect[i] = minAreaRect( Mat(contours[i]) );
// Draw contours + rotated rects
drawing = Mat::zeros(temp.size(), CV_8U );
Point2f rect_points[4];
for( int i = 0; i< minRect.size(); i++ ){
if((float)minRect[i].boundingRect().height/(float)minRect[i].boundingRect().width > 3.0 && (float)minRect[i].boundingRect().height/(float)minRect[i].boundingRect().width < 4.9){
// rotated rectangle
minRect[i].points(rect_points);
for(int j = 0; j < 4; j++) line(drawing, rect_points[j], rect_points[(j+1)%4], Scalar(255), 1, 8);
//found = minRect[i].boundingRect().contains(Point(459, 512));
if(minRect[i].boundingRect().area() > 1000)
rect_no = i;
}
}
center = computeCentroid(drawing);
cv::floodFill(drawing, center, cv::Scalar(255));
drawing.convertTo(imgOut, CV_16U, 257.0);
imgIn.copyTo(imgOut, drawing);
// Calculate Wire SR_min
// Get angle of Wire
angle = (90 - (-1 * minRect[rect_no].angle))*(CV_PI/180);
for(int i = 0;i < SizeY;i++){
for(int j = 0;j < SizeX;j++){
if(imgOut.at<ushort>(i, j) != 0)
lineProfile.at<ushort>(i, j) = imgOut.at<ushort>((unsigned short)(i/cos(angle)), j);
}
}
for(int i = 0;i < SizeY;i++){
for(int j = 0;j < SizeX;j++){
*Wire++ = lineProfile.at<ushort>(i, j);//imgOut.at<ushort>(i, j);
}
}
If you know the coordinates of the beginning and the end of your line, getting the values at each point on the line should be easy with OpenCV's LineIterator. Feed it your image and your two points and let it work its magic.
If you are able to binarize the detected object image , then you could possibly able to apply Houghlines function of OpenCv . You can find it in the below link
http://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.html
I have the following code:
findContours( src, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE );
Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
double largest_area = 0;
for( int i = 0; i < contours.size(); i++) { // get the largest contour
area = fabs( contourArea( contours[i] ) );
if( area >= largest_area ){
largest_area = area;
largest_contours.clear();
largest_contours.push_back( contours[i] );
}
}
if( largest_area >= 3000 ){ // draw the largest contour if exceeded minimum largest area
drawContours( drawing, largest_contours, -1, Scalar(0,0,255), 2 );
}
... which produces the following output image:
I want to get coordinates of four points (marked with green), is that possible?
Do you trying to find corners of rectangle in perspective?
You may want to try several solutions:
Use HoughLines for line detection and find their intersection.
Use Generalized Hough Transform
Use Harris corner detector. But you need to filter extra corners.
For similar task I used following procedure (it works fine in my case):
do cv::approxPolyDP for input contour with increasing epsilon parameter until it returns 4 or less polylines. If it returns 4 polylines you may get 4 corner points exact what you need. If it returns less than 4 polylines most probably something is wrong.
A while ago I asked a question about square detection and karlphillip came up with a decent result.
Now I want to take this a step further and find squares which edge aren't fully visible. Take a look at this example:
Any ideas? I'm working with karlphillips code:
void find_squares(Mat& image, vector<vector<Point> >& squares)
{
// blur will enhance edge detection
Mat blurred(image);
medianBlur(image, blurred, 9);
Mat gray0(blurred.size(), CV_8U), gray;
vector<vector<Point> > contours;
// find squares in every color plane of the image
for (int c = 0; c < 3; c++)
{
int ch[] = {c, 0};
mixChannels(&blurred, 1, &gray0, 1, ch, 1);
// try several threshold levels
const int threshold_level = 2;
for (int l = 0; l < threshold_level; l++)
{
// Use Canny instead of zero threshold level!
// Canny helps to catch squares with gradient shading
if (l == 0)
{
Canny(gray0, gray, 10, 20, 3); //
// Dilate helps to remove potential holes between edge segments
dilate(gray, gray, Mat(), Point(-1,-1));
}
else
{
gray = gray0 >= (l+1) * 255 / threshold_level;
}
// Find contours and store them in a list
findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
// Test contours
vector<Point> approx;
for (size_t i = 0; i < contours.size(); i++)
{
// approximate contour with accuracy proportional
// to the contour perimeter
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);
// Note: absolute value of an area is used because
// area may be positive or negative - in accordance with the
// contour orientation
if (approx.size() == 4 &&
fabs(contourArea(Mat(approx))) > 1000 &&
isContourConvex(Mat(approx)))
{
double maxCosine = 0;
for (int j = 2; j < 5; j++)
{
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
if (maxCosine < 0.3)
squares.push_back(approx);
}
}
}
}
}
You might try using HoughLines to detect the four sides of the square. Next, locate the four resulting line intersections to detect the corners. The Hough transform is fairly robust to noise and occlusions, so it could be useful here. Also, here is an interactive demo showing how the Hough transform works (I thought it was cool at least :). Here is one of my previous answers that detects a laser cross center showing most of the same math (except it just finds a single corner).
You will probably have multiple lines on each side, but locating the intersections should help to determine the inliers vs. outliers. Once you've located candidate corners, you can also filter these candidates by area or how "square-like" the polygon is.
EDIT : All these answers with code and images were making me think my answer was a bit lacking :) So, here is an implementation of how you could do this:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
Point2f computeIntersect(Vec2f line1, Vec2f line2);
vector<Point2f> lineToPointPair(Vec2f line);
bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta);
int main(int argc, char* argv[])
{
Mat occludedSquare = imread("Square.jpg");
resize(occludedSquare, occludedSquare, Size(0, 0), 0.25, 0.25);
Mat occludedSquare8u;
cvtColor(occludedSquare, occludedSquare8u, CV_BGR2GRAY);
Mat thresh;
threshold(occludedSquare8u, thresh, 200.0, 255.0, THRESH_BINARY);
GaussianBlur(thresh, thresh, Size(7, 7), 2.0, 2.0);
Mat edges;
Canny(thresh, edges, 66.0, 133.0, 3);
vector<Vec2f> lines;
HoughLines( edges, lines, 1, CV_PI/180, 50, 0, 0 );
cout << "Detected " << lines.size() << " lines." << endl;
// compute the intersection from the lines detected...
vector<Point2f> intersections;
for( size_t i = 0; i < lines.size(); i++ )
{
for(size_t j = 0; j < lines.size(); j++)
{
Vec2f line1 = lines[i];
Vec2f line2 = lines[j];
if(acceptLinePair(line1, line2, CV_PI / 32))
{
Point2f intersection = computeIntersect(line1, line2);
intersections.push_back(intersection);
}
}
}
if(intersections.size() > 0)
{
vector<Point2f>::iterator i;
for(i = intersections.begin(); i != intersections.end(); ++i)
{
cout << "Intersection is " << i->x << ", " << i->y << endl;
circle(occludedSquare, *i, 1, Scalar(0, 255, 0), 3);
}
}
imshow("intersect", occludedSquare);
waitKey();
return 0;
}
bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta)
{
float theta1 = line1[1], theta2 = line2[1];
if(theta1 < minTheta)
{
theta1 += CV_PI; // dealing with 0 and 180 ambiguities...
}
if(theta2 < minTheta)
{
theta2 += CV_PI; // dealing with 0 and 180 ambiguities...
}
return abs(theta1 - theta2) > minTheta;
}
// the long nasty wikipedia line-intersection equation...bleh...
Point2f computeIntersect(Vec2f line1, Vec2f line2)
{
vector<Point2f> p1 = lineToPointPair(line1);
vector<Point2f> p2 = lineToPointPair(line2);
float denom = (p1[0].x - p1[1].x)*(p2[0].y - p2[1].y) - (p1[0].y - p1[1].y)*(p2[0].x - p2[1].x);
Point2f intersect(((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].x - p2[1].x) -
(p1[0].x - p1[1].x)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom,
((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].y - p2[1].y) -
(p1[0].y - p1[1].y)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom);
return intersect;
}
vector<Point2f> lineToPointPair(Vec2f line)
{
vector<Point2f> points;
float r = line[0], t = line[1];
double cos_t = cos(t), sin_t = sin(t);
double x0 = r*cos_t, y0 = r*sin_t;
double alpha = 1000;
points.push_back(Point2f(x0 + alpha*(-sin_t), y0 + alpha*cos_t));
points.push_back(Point2f(x0 - alpha*(-sin_t), y0 - alpha*cos_t));
return points;
}
NOTE : The main reason I resized the image was so I could see it on my screen, and speed-up processing.
Canny
This uses Canny edge detection to help greatly reduce the number of lines detected after thresholding.
Hough transform
Then the Hough transform is used to detect the sides of the square.
Intersections
Finally, we compute the intersections of all the line pairs.
Hope that helps!
I tried to use convex hull method which is pretty simple.
Here you find convex hull of the contour detected. It removes the convexity defects at the bottom of paper.
Below is the code (in OpenCV-Python):
import cv2
import numpy as np
img = cv2.imread('sof.jpg')
img = cv2.resize(img,(500,500))
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,127,255,0)
contours,hier = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt)>5000: # remove small areas like noise etc
hull = cv2.convexHull(cnt) # find the convex hull of contour
hull = cv2.approxPolyDP(hull,0.1*cv2.arcLength(hull,True),True)
if len(hull)==4:
cv2.drawContours(img,[hull],0,(0,255,0),2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
(Here, i haven't found square in all planes. Do it yourself if you want.)
Below is the result i got:
I hope this is what you needed.
1st: start experimenting with threshold techniques to isolate the white paper sheet from the rest of the image. This is a simple way:
Mat new_img = imread(argv[1]);
double thres = 200;
double color = 255;
threshold(new_img, new_img, thres, color, CV_THRESH_BINARY);
imwrite("thres.png", new_img);
but there are other alternatives that could provide better result. One is to investigate inRange(), and another is to detect through color by converting the image to the HSV color space.
This thread also provides an interest discussion on the subject.
2nd: after you execute one of this procedures, you could try to feed the result directly into find_squares():
An alternative to find_squares() is to implement the bounding box technique, which has the potential to provide a more accurate detection of the rectangular area (provided that you have a perfect result of threshold). I've used it here and here. It's worth noting that OpenCV has it's own bounding box tutorial.
Another approach besides find_squares(), as pointed by Abid on his answer, is to use the convexHull method. Check OpenCV's C++ tutorial on this method for code.
convert to lab space
use kmeans for 2 clusters
detect suqares one internal cluster it will solve many thing in the rgb space
I am trying to find the edges of the centered box in this image:
I have tried using a HoughLines using dRho=img_width/1000, dTheta=pi/180, and threshold=250
It works great on this image, scaled to 1/3 of the size, but on the full size image it just gets lines everywhere in every direction...
What can I do to tune this to be more accurate?
The code to achieve the result below is a slight modification of the one presented in this answer: how to detect a square:
The original program can be found inside OpenCV, it's called squares.cpp. The code below was modified to search squares only in the first color plane, but as it still detects many squares, at the end of the program I discard all of them except the first, and then call draw_squares() to show what was detected. You can change this easilly to draw all of them and see everything that was detected.
You can do all sorts of thing from now own, including setting a (ROI) region of interest to extract the area that's inside the square (ignore everything else around it).
You can see that the detected rectangle is not perfectly aligned with the lines in the image. You should perform some pre-processing (erode?) operations in the image to decrease the thickness of lines and improve the detection. But from here on it's all on you:
#include <cv.h>
#include <highgui.h>
using namespace cv;
double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) {
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
void find_squares(Mat& image, vector<vector<Point> >& squares)
{
// TODO: pre-processing
// blur will enhance edge detection
Mat blurred(image);
medianBlur(image, blurred, 9);
Mat gray0(blurred.size(), CV_8U), gray;
vector<vector<Point> > contours;
// find squares in the first color plane.
for (int c = 0; c < 1; c++)
{
int ch[] = {c, 0};
mixChannels(&blurred, 1, &gray0, 1, ch, 1);
// try several threshold levels
const int threshold_level = 2;
for (int l = 0; l < threshold_level; l++)
{
// Use Canny instead of zero threshold level!
// Canny helps to catch squares with gradient shading
if (l == 0)
{
Canny(gray0, gray, 10, 20, 3); //
// Dilate helps to remove potential holes between edge segments
dilate(gray, gray, Mat(), Point(-1,-1));
}
else
{
gray = gray0 >= (l+1) * 255 / threshold_level;
}
// Find contours and store them in a list
findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
// Test contours
vector<Point> approx;
for (size_t i = 0; i < contours.size(); i++)
{
// approximate contour with accuracy proportional
// to the contour perimeter
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);
// Note: absolute value of an area is used because
// area may be positive or negative - in accordance with the
// contour orientation
if (approx.size() == 4 &&
fabs(contourArea(Mat(approx))) > 1000 &&
isContourConvex(Mat(approx)))
{
double maxCosine = 0;
for (int j = 2; j < 5; j++)
{
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
if (maxCosine < 0.3)
squares.push_back(approx);
}
}
}
}
}
void draw_squares(Mat& img, vector<vector<Point> > squares)
{
for (int i = 0; i < squares.size(); i++)
{
for (int j = 0; j < squares[i].size(); j++)
{
cv::line(img, squares[i][j], squares[i][(j+1) % 4], cv::Scalar(0, 255, 0), 1, CV_AA);
}
}
}
int main(int argc, char* argv[])
{
Mat img = imread(argv[1]);
vector<vector<Point> > squares;
find_squares(img, squares);
std::cout << "* " << squares.size() << " squares were found." << std::endl;
// Ignore all the detected squares and draw just the first found
vector<vector<Point> > tmp;
if (squares.size() > 0)
{
tmp.push_back(squares[0]);
draw_squares(img, tmp);
}
//imshow("squares", img);
//cvWaitKey(0);
imwrite("out.png", img);
return 0;
}
when resizing the image, the image is normally first blurred with a filter, e.g. Gaussian, in order to get rid of high frequencies. The fact that resized one works better is likely because your original image is somehow noisy.
Try blur the image first, e.g. with cv::GaussianBlur(src, target, Size(0,0), 1.5), then it should be equivalent to resizing. (It forgot the theory, if it does not work, try 3 and 6 as well)
Try using a preprocessing pass with the erosion filter. It will give you the same effect as the downscaling - the lines will become thinner and will not disappear at the same time.
The "Blur" filter is also a good idea, as chaiy says.
This way (with blur) it will become something like http://www.ic.uff.br/~laffernandes/projects/kht/index.html (Kernel Based Hough Transform)