I asked a previous question here and following the advice from the answer I built the below program which I thought would detect large rectangle but it doesn't detect the rectangle at all. It does work on this image though.
Original Image
Desired Image
I want the solution to work on not only this image but different images of this kind. Major part of the code below is from different answers on SO
My full program:
#include <cv.h>
#include <highgui.h>
using namespace cv;
using namespace std;
double angle( Point pt1, Point pt2, 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)
{
// 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);
}
}
}
}
}
void find_largest_square(const vector<vector <Point> >& squares, vector<Point>& biggest_square) {
if (!squares.size()) {
return;
}
int max_width = 0;
int max_height = 0;
int max_square_idx = 0;
const int n_points = 4;
for (size_t i = 0; i < squares.size(); i++) {
Rect rectangle = boundingRect(Mat(squares[i]));
if ((rectangle.width >= max_width) && (rectangle.height >= max_height)) {
max_width = rectangle.width;
max_height = rectangle.height;
max_square_idx = i;
}
}
biggest_square = squares[max_square_idx];
}
int main(int argc, char* argv[])
{
Mat img = imread(argv[1]);
if (img.empty())
{
cout << "!!! imread() failed to open target image" << endl;
return -1;
}
vector< vector< Point> > squares;
find_squares(img, squares);
vector<Point> largest_square;
find_largest_square(squares, largest_square);
for (int i = 0; i < 4; ++i) {
line(img, largest_square[i], largest_square[(i+1)%4], Scalar(0, 255, 0), 1, CV_AA);
}
imwrite("squares.png", img);
imshow("squares", img);
waitKey(0);
return 0;
}
I think you can do it easily using findContours function - http://docs.opencv.org/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.html The biggest contour (or eventually second biggest) should be contour of black rectangle. Then just find the smallest rectangle which will surround this contour (just find points with the biggest/smallest x/y coordinates).
Related
I'm trying to detect a paper sheet in an image just like Camscanner.
I've followed the this link for this implementation.
This detects the paper sheet on dark backgrounds accurately but not on a light background.
Sample image:
Code:
void find_squares(Mat& image, vector<vector<cv::Point> >& squares){
// blur will enhance edge detection
Mat blurred(image);
medianBlur(image, blurred, 9);
Mat gray0(blurred.size(), CV_8U), gray;
vector<vector<cv::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(), cv::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<cv::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);
}
}
}
}}
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);}
I'm trying to detect the following book, using findcontours but it cannot be detected at all and I get exception because there is no convex hull.
I tried to blur, dilate, canny detection, with no success at all.
I hope to get a solution for finding a rectangular paper/book using openCV.
Please let me know if you have further questions or need resources.
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
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)
{
// blur will enhance edge detection
Mat blurred(image);
Mat dst;
medianBlur(image, dst, 9);
Mat gray0(dst.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(&dst, 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);
}
}
}
}
}
cv::Mat debugSquares(std::vector<std::vector<cv::Point> > squares, cv::Mat image)
{
for (int i = 0; i< squares.size(); i++) {
// draw contour
cv::drawContours(image, squares, i, cv::Scalar(255, 0, 0), 1, 8, std::vector<cv::Vec4i>(), 0, cv::Point());
// draw bounding rect
cv::Rect rect = boundingRect(cv::Mat(squares[i]));
cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0, 255, 0), 2, 8, 0);
// draw rotated rect
cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i]));
cv::Point2f rect_points[4];
minRect.points(rect_points);
for (int j = 0; j < 4; j++) {
cv::line(image, rect_points[j], rect_points[(j + 1) % 4], cv::Scalar(0, 0, 255), 1, 8); // blue
}
}
return image;
}
static std::vector<cv::Point> extremePoints(std::vector<cv::Point>pts)
{
int xmin = 0, ymin = 0, xmax = -1, ymax = -1, i;
Point ptxmin, ptymin, ptxmax, ptymax;
Point pt = pts[0];
ptxmin = ptymin = ptxmax = ptymax = pt;
xmin = xmax = pt.x;
ymin = ymax = pt.y;
for (size_t i = 1; i < pts.size(); i++)
{
pt = pts[i];
if (xmin > pt.x)
{
xmin = pt.x;
ptxmin = pt;
}
if (xmax < pt.x)
{
xmax = pt.x;
ptxmax = pt;
}
if (ymin > pt.y)
{
ymin = pt.y;
ptymin = pt;
}
if (ymax < pt.y)
{
ymax = pt.y;
ptymax = pt;
}
}
std::vector<cv::Point> res;
res.push_back(ptxmin);
res.push_back(ptxmax);
res.push_back(ptymin);
res.push_back(ptymax);
return res;
}
void sortCorners(std::vector<cv::Point2f>& corners)
{
std::vector<cv::Point2f> top, bot;
cv::Point2f center;
// Get mass center
for (int i = 0; i < corners.size(); i++)
center += corners[i];
center *= (1. / corners.size());
for (int i = 0; i < corners.size(); i++)
{
if (corners[i].y < center.y)
top.push_back(corners[i]);
else
bot.push_back(corners[i]);
}
corners.clear();
if (top.size() == 2 && bot.size() == 2) {
cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];
corners.push_back(tl);
corners.push_back(tr);
corners.push_back(br);
corners.push_back(bl);
}
}
int main(int, char**)
{
int largest_area = 0;
int largest_contour_index = 0;
cv::Rect bounding_rect;
Mat src, edges;
src = imread("20628991_10159154614610574_1244594322_o.jpg");
cvtColor(src, edges, COLOR_BGR2GRAY);
GaussianBlur(edges, edges, Size(5, 5), 1.5, 1.5);
erode(edges, edges, Mat());// these lines may need to be optimized
dilate(edges, edges, Mat());
dilate(edges, edges, Mat());
erode(edges, edges, Mat());
Canny(edges, edges, 150, 150, 3); // canny parameters may need to be optimized
imshow("edges", edges);
vector<Point> selected;
vector<vector<Point> > contours;
findContours(edges, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
for (size_t i = 0; i < contours.size(); i++)
{
Rect minRect = boundingRect(contours[i]);
if (minRect.width > 150 & minRect.height > 150) // this line also need to be optimized
{
selected.insert(selected.end(), contours[i].begin(), contours[i].end());
}
}
convexHull(selected, selected);
RotatedRect minRect = minAreaRect(selected);
std::vector<cv::Point> corner_points = extremePoints(selected);
std::vector<cv::Point2f> corners;
corners.push_back(corner_points[0]);
corners.push_back(corner_points[1]);
corners.push_back(corner_points[2]);
corners.push_back(corner_points[3]);
sortCorners(corners);
cv::Mat quad = cv::Mat::zeros(norm(corners[1] - corners[2]), norm(corners[2] - corners[3]), CV_8UC3);
std::vector<cv::Point2f> quad_pts;
quad_pts.push_back(cv::Point2f(0, 0));
quad_pts.push_back(cv::Point2f(quad.cols, 0));
quad_pts.push_back(cv::Point2f(quad.cols, quad.rows));
quad_pts.push_back(cv::Point2f(0, quad.rows));
cv::Mat transmtx = cv::getPerspectiveTransform(corners, quad_pts);
cv::warpPerspective(src, quad, transmtx, quad.size());
resize(quad, quad, Size(), 0.25, 0.25); // you can remove this line to keep the image original size
imshow("quad", quad);
polylines(src, selected, true, Scalar(0, 0, 255), 2);
resize(src, src, Size(), 0.5, 0.5); // you can remove this line to keep the image original size
imshow("result", src);
waitKey(0);
return 0;
}
Strange, I did it with exactly that (blur, dilate, canny):
The code (in Python, but there's nothing but OpenCV function calls, so should be easy to follow; as one of the references I used this answer, which is in C++, it also shows how to correct the perspective and turn it into a rectangle):
import numpy as np
import cv2
img = cv2.imread('sngo1.jpg')
#resize and create a copy for future drawing
resize_coeff = 0.5
w, h, c = img.shape
img_in = cv2.resize(img, (int(resize_coeff*h), int(resize_coeff*w)))
img_out = img_in.copy()
#median and canny
img_in = cv2.medianBlur(img_in, 5)
img_in = cv2.Canny(img_in, 100, 200)
#morphological close for our edges
kernel = np.ones((17, 17), np.uint8)
img_in = cv2.morphologyEx(img_in, cv2.MORPH_CLOSE, kernel, iterations = 1)
#find contours, get max by area
img_in, contours, hierarchy = cv2.findContours(img_in, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
max_index, max_area = max(enumerate([cv2.contourArea(x) for x in contours]), key = lambda x: x[1])
max_contour = contours[max_index]
#approximage it with a quadrangle
approx = cv2.approxPolyDP(max_contour, 0.1*cv2.arcLength(max_contour, True), True)
approx = approx[:,0,:]
cv2.drawContours(img_out, [approx], 0, (255, 0, 0), 2)
cv2.imwrite("result.png", img_out)
Suppose if we are working on an image, is there any way to access the pixels inside the contour?
I have already found the contour using the function findContours() and even found the moments but I couldn't find the pixels inside the contour.
Any suggestions are Welcome!!
Thank you!
As #Miki already mentioned you can use connectedComponents to perform a labeling. Then you iterate through the bounding box of your object like #Amitay Nachmani suggested. But instead of using pointPolygonTest you can check if the value at your current positions matches your current label Here is a small example:
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <vector>
using namespace cv;
using namespace std;
Mat binary, labels, stats, centroids;
int main()
{
Mat src = imread("C:\\Users\\phili\\Pictures\\t06-4.png",0);
threshold(src, binary, 0, 255, CV_THRESH_OTSU);
int nLabels = connectedComponentsWithStats(binary, labels, stats, centroids);
vector<vector<Point>> blobs(nLabels-1);
for (int i = 1; i < nLabels; i++) //0 is background
{
//get bounding rect
int left = stats.at<int>(i, CC_STAT_LEFT) ;
int top = stats.at<int>(i, CC_STAT_TOP);
int width = stats.at<int>(i, CC_STAT_WIDTH);
int height = stats.at<int>(i, CC_STAT_HEIGHT);
blobs[i - 1].reserve(width*height);
int x_end = left + width;
int y_end = top + height;
for (int x = left; x < x_end; x++)
{
for (int y = top; y < y_end; y++)
{
Point p(x, y);
if (i == labels.at<int>(p))
{
blobs[i-1].push_back(p);
}
}
}
}
}
EDIT:
Since youre using OpenCV 2.4 there are two ways to achieve the same results.
First you could use findContours to detect the blobs, then draw them (filled) into a new image with a specific color as label (be aware that your blobs could contain holes) Then iterate through the image inside the bounding rectangle of each contour and get all points with the label of your current contour. If you just iterate through the bounding rectangle inside your binary image, you have problems with objects overlapping the bounding rectangle.
Here is the code:
int getBlobs(Mat binary, vector<vector<Point>> & blobs)
{
Mat labels(src.size(), CV_32S);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
blobs.clear();
blobs.reserve(contours.size());
int count = 1; //0 is background
for (int i = 0; i < contours.size(); i++) // iterate through each contour.
{
//if contour[i] is not a hole
if (hierarchy[i][3] == -1)
{
//draw contour without holes
drawContours(labels, contours, i, Scalar(count),CV_FILLED, 0, hierarchy, 2, Point());
Rect rect = boundingRect(contours[i]);
int left = rect.x;
int top = rect.y;
int width = rect.width;
int height = rect.height;
int x_end = left + width;
int y_end = top + height;
vector<Point> blob;
blob.reserve(width*height);
for (size_t x = left; x < x_end; x++)
{
for (size_t y = top; y < y_end; y++)
{
Point p(x, y);
if (count == labels.at<int>(p))
{
blob.push_back(p);
}
}
}
blobs.push_back(blob);
count++;
}
}
count--;
return count;
}
Second you can perform your own labling with floodfill. Therefore you iterate through your image and start floodfill for every white pixel, iterate through the bounding rectangle and get all points that have the same seedColor.
Here is the code:
int labeling(Mat binary, vector<vector<Point>> &blobs)
{
FindBlobs(binary, blobs);
return blobs.size();
}
with
void FindBlobs(const Mat &binary, vector<vector<Point>> &blobs)
{
blobs.clear();
// Fill the label_image with the blobs
// 0 - background
// 1 - unlabelled foreground
// 2+ - labelled foreground
cv::Mat label_image;
binary.convertTo(label_image, CV_32FC1);
float label_count = 2; // starts at 2 because 0,1 are used already
for (int y = 0; y < label_image.rows; y++) {
float *row = (float*)label_image.ptr(y);
for (int x = 0; x < label_image.cols; x++) {
if (row[x] != 255) {
continue;
}
cv::Rect rect;
cv::floodFill(label_image, Point(x, y), Scalar(label_count), &rect, Scalar(0), Scalar(0), 4 );
vector<Point> blob;
blob.reserve(rect.width*rect.height);
for (int i = rect.y; i < (rect.y + rect.height); i++) {
float *row2 = (float*)label_image.ptr(i);
for (int j = rect.x; j < (rect.x + rect.width); j++) {
if (row2[j] != label_count)
{
continue;
}
blob.push_back(Point(j, i));
}
}
blobs.push_back(blob);
label_count++;
}
}
}
I used this image:
And here are the bounding boxes and the points inside the contour for visualization:
Create a new image with filled contours using fillPoly.
fillPoly(filledImage, contours, Scalar(255, 255, 255));
Then find the non-zero pixels within that image using findNonZero.
vector<Point> indices;
findNonZero(filledImage, indices);
The "indices" result refer to pixels inside the contour
Use the pointPolygonTest http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=pointpolygontest#pointpolygontest on the all the pixels inside the bounding box of the contour contour.
I have encountered a problem to partition binarized image with boxes/ subimage containing object (Note: the boxes can be irregular while the object is in circle of any other primitive shapes). This could be explained with images as below:
Figure 1: Image with circle as objects of interest
Figure 2: Image with boxes of arbitrary size containing objects of interest
Thus, any opinion that this can be done?
Since you mentioned that:
the boxes can be irregular
you can use the Voronoi diagram (computed by distanceTransform):
Code:
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
Mat1f dist;
Mat1i labels;
distanceTransform(img, dist, labels, CV_DIST_L2, 3, DIST_LABEL_CCOMP);
// Show result
Mat1b labels1b;
labels.convertTo(labels1b, CV_8U);
normalize(labels1b, labels1b, 0, 255, NORM_MINMAX);
Mat3b res;
applyColorMap(labels1b, res, COLORMAP_JET);
res.setTo(Scalar(0,0,0), ~img);
imshow("Result", res);
waitKey();
return 0;
}
Update
If you need the boxes to be rectangles, you can look at recursive XY Cut algorithm. Here is a modified version of XY Cut algorithm, that makes the rectangles not touching the foreground objects, so that the sum of all rectangles covers the whole image area. Here I inverted the image since usually black is background, and white is foreground.
Code:
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
vector<Rect> XYCut_projH(const Mat1b& src, Rect roi)
{
Mat1b projH;
reduce(src(roi), projH, 1, CV_REDUCE_MAX);
vector<Rect> rects;
bool bOut = true;
vector<int> coords;
coords.push_back(0);
for (int i = 0; i < projH.rows; ++i)
{
if (bOut && projH(i) > 0)
{
coords.back() = (coords.back() + i) / 2;
bOut = false;
}
else if (!bOut && projH(i) == 0)
{
coords.push_back(i);
bOut = true;
}
}
coords.front() = 0;
coords.back() = projH.rows;
if (coords.size() <= 1) return rects;
for (int i = 0; i < coords.size() - 1; ++i)
{
Rect r(0, coords[i], src.cols, coords[i + 1] - coords[i]);
r = (r + roi.tl()) & roi;
rects.push_back(r);
}
return rects;
}
vector<Rect> XYCut_projV(const Mat1b& src, Rect roi)
{
Mat1b projV;
reduce(src(roi), projV, 0, CV_REDUCE_MAX);
vector<Rect> rects;
bool bOut = true;
vector<int> coords;
coords.push_back(0);
for (int i = 0; i < projV.cols; ++i)
{
if (bOut && projV(i) > 0)
{
coords.back() = (coords.back() + i) / 2;
bOut = false;
}
else if (!bOut && projV(i) == 0)
{
coords.push_back(i);
bOut = true;
}
}
coords.front() = 0;
coords.back() = projV.cols;
if (coords.size() <= 1) return rects;
for (int i = 0; i < coords.size() - 1; ++i)
{
Rect r(coords[i], 0, coords[i + 1] - coords[i], src.rows);
r = (r + roi.tl()) & roi;
rects.push_back(r);
}
return rects;
}
void XYCut_step(const Mat1b& src, Rect roi, vector<Rect>& rects, bool bAlternate)
{
vector<Rect> step;
if (bAlternate)
{
step = XYCut_projH(src, roi);
if ((step.size() == 1) && (step[0] == roi) && (XYCut_projV(src, roi).size() == 1))
{
rects.push_back(roi);
return;
}
}
else
{
step = XYCut_projV(src, roi);
if ((step.size() == 1) && (step[0] == roi) && (XYCut_projH(src, roi).size() == 1))
{
rects.push_back(roi);
return;
}
}
for (int i = 0; i < step.size(); ++i)
{
XYCut_step(src, step[i], rects, !bAlternate);
}
}
void XYCut(const Mat1b& src, vector<Rect>& rects)
{
bool bAlternate = true;
Rect roi(0, 0, src.cols, src.rows);
XYCut_step(src, roi, rects, bAlternate);
}
int main()
{
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// invert image, if needed
img = ~img;
// Apply (modified) XY Cut
vector<Rect> rects;
XYCut(img, rects);
// Show results
Mat3b res;
cvtColor(img, res, COLOR_GRAY2BGR);
for (int i = 0; i < rects.size(); ++i)
{
rectangle(res, rects[i], Scalar(0,255,0));
}
imshow("Result", res);
waitKey();
return 0;
}
Note that this algorithm works only if it's possible to make a cut along X or Y dimension, i.e. there is a horizontal or vertical line with all background pixels. This means that this won't work in a very cluttered image.
I have following picture and try to find the largest rectangle with OpenCV with these lines
std::vector< std::vector<cv::Point> > contours;
cv::findContours(result,contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
But the statements above causes memory heap error. Can anyone give me a clue why this is happening? I have been stretching my hairs for last couple of hours.
I think it's something to do with cv::Point allocator since call stack indicates it.
Update: I just ran the program with CvFindContours instead without any problem. So it must be OpenCV 2.3.1.
Update2: Thanks to #karlphillip answer, I revisited my project and it was my Visual Studio project setting. I was linking MFC as static library because of annoying memork leak message. That was the cause of the problem. When I use MFC as shared DLL, the problem went away.
I've just tested the following application with OpenCV 2.3.1 on both Linux and Windows XP (32bits) and I had no problems.
Unless you can write a minimal application to reproduce the problem you are observing, this is as far as I go.
This is the input image, and the code is right below:
#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)
{
// 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);
}
}
}
}
}
int main()
{
Mat img = imread("paper.jpg");
vector<vector<Point> > squares;
find_squares(img, squares);
std::cout << "squares size: " << squares.size() << std::endl;
getchar();
return 0;
}