I've read this post, but even after using cv::threshold to create a really binarry image, I still get ~500 contours. What am I doing wrong?
Shouldn't cv::findContours return only 13 contours since there are clear 13 blobs?
Mat img = imread("img.jpg", CV_LOAD_IMAGE_GRAYSCALE);
Mat img_thresh;
threshold(img, img_thresh, 0, 255, CV_THRESH_BINARY);
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
cv::findContours(img_thresh, contours, hierarchy, RetrievalModes::RETR_TREE, ContourApproximationModes::CHAIN_APPROX_SIMPLE);
RNG rng(12345);
Mat drawing = Mat::zeros(img_thresh.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));
drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
}
imshow("drawing", drawing);
waitKey();
UPDATE1
Using cv::RETR_EXTERNAL instead of cv::RETR_TREE, but still return much more contours than should be.
If you check your binary image you will see there are a lot of independent contours:
So you first need to clean up them by eroding and dilating as below code:
And you will get this result:
Which is cleaner than the original.
It is all the code:
cv::namedWindow("result", cv::WINDOW_FREERATIO);
cv::Mat img = cv::imread(R"(rUYLL.png)");
// to gray
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, gray, 0, 255, cv::THRESH_BINARY);
cv::erode(gray, gray, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
cv::dilate(gray, gray, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
std::vector<std::vector<cv::Point> > contours;
cv::findContours(gray, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::drawContours(img, contours, -1, cv::Scalar(0, 255, 0), 2, 8);
cv::imshow("result", img);
cv::waitKey();
And it is the output:
Hope it helps!
And one simplest way which you can also consider if it works for you, just increase the lower threshold from 0 to 80, and DONE
cv::threshold(gray, gray, 80, 255, cv::THRESH_BINARY);
JUST PLAY WITH THRESHOLD and check the result.
The same output just with changing the threshold value:
Mat frame;
Mat frame2;
Mat output_frame;
Mat imgray;
Mat imgCanny;
vector<vector<Point> > contours;
vector<Point> approx;
Mat img = imread("abc.jpg");
cvtColor(img, imgray, COLOR_BGR2GRAY);
Canny(imgray, imgCanny, 10, 100, 3, false);
findContours(imgCanny, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
double eps = 0.1 * arcLength(contours[0], true);
approxPolyDP(contours[0], approx, 1, true);
drawContours(img, approx, 0, (0, 255, 0), 1); // Here has Error..
I studied about OpenCV, but the drawContours method(?) is strange.
I mean other drawContours is done (drawContours(img,contours,0,(0,255,0),1);)
But drawContours(img, approx, 0, (0, 255, 0), 1); has error.
Why?
I confirm that the approx has data (4 dot points)
input of drawContours should be vector<vector<Point> > .
here is minor modification of your code.
Mat frame;
Mat frame2;
Mat output_frame;
Mat imgray;
Mat imgCanny;
vector<vector<Point> > contours;
vector<Point> approx;
Mat img = imread("abc.jpg");
cvtColor(img, imgray, COLOR_BGR2GRAY);
Canny(imgray, imgCanny, 10, 100, 3, false);
findContours(imgCanny, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
double eps = 0.1 * arcLength(contours[0], true);
approxPolyDP(contours[0], approx, 1, true);
vector<vector<Point> > approx_t;
approx_t.push_back(approx);
drawContours(img, approx_t, 0, (0, 255, 0), 1);
I am working on a code to detect the edges of boxes in a picture, but the boxes attached to the edge of the picture have missing lines.
as seen in the previous above the first square have only 2 lines appears.
I am using open cv C++
here is my code
cv::Mat src;
cv::Mat gray;
cv::Mat dst;
std::vector<std::vector<cv::Point> > contours;
std::vector<std::vector<cv::Point> > contours2;
std::vector<cv::Point> approx;
Mat img = imread("shapes-noisy.jpg", CV_LOAD_IMAGE_COLOR);
Mat _color = img.clone();
threshold(img, img, 250, 255, 0);
src = img;
cv::cvtColor(src, gray, CV_BGR2GRAY);
Mat img6, img7, img8, img9, img10;
cv::threshold(gray, img6, 250, 255.0, THRESH_BINARY);
Mat element = getStructuringElement( MORPH_RECT,Size(3, 3 ), Point( 1, 1 ) );
dilate( img6, img6, element);
Laplacian(img6, img7, CV_16S, 3, 1, 0, BORDER_DEFAULT);
convertScaleAbs(img7, img8);
vector<Vec4i> hierarchy;
RNG rng(0);
Scalar value;
cv::findContours(img8.clone(), contours, hierarchy,CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
Mat drawing,drawing2;
medianBlur(img8, drawing, 3);
threshold(drawing, drawing, 255, 0, 1);
threshold(drawing, drawing2, 255, 0, 1);
cv::findContours(img8.clone(), contours2, hierarchy,CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
for( int i = 0; i< contours.size(); i++ )
{
cv::approxPolyDP(cv::Mat(contours[i]), approx, 10, true);
if(approx.size() > 3){
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
drawContours( drawing2, contours2, i, color, 2, 8, hierarchy, 0, Point(0,0) );
}
}
imshow("approx", drawing2);
My code is the same as this tutorial.
When I see the result image after using cv::watershed(), there is a object(upper-right) that I want to find out, but it's missing.
There are indeed six marks in image after using cv::drawContours().
Is this normal because the inaccuracy of the watershed algorithm exist?
Here is part of my code:
Mat src = imread("result01.png");
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
// noise removal
Mat kernel = Mat::ones(3, 3, CV_8UC1);
Mat opening;
morphologyEx(thresh, opening, MORPH_OPEN, kernel, Point(-1, -1), 2);
// Perform the distance transform algorithm
Mat dist_transform;
distanceTransform(opening, dist_transform, CV_DIST_L2, 5);
// Normalize the distance image for range = {0.0, 1.0}
// so we can visualize and threshold it
normalize(dist_transform, dist_transform, 0, 1., NORM_MINMAX);
// Threshold to obtain the peaks
// This will be the markers for the foreground objects
Mat dist_thresh;
threshold(dist_transform, dist_thresh, 0.5, 1., CV_THRESH_BINARY);
Mat dist_8u;
dist_thresh.convertTo(dist_8u, CV_8U);
// Find total markers
vector<vector<Point> > contours;
findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// Create the marker image for the watershed algorithm
Mat markers = Mat::zeros(dist_thresh.size(), CV_32SC1);
// Draw the foreground markers
for (size_t i = 0; i < contours.size(); i++)
drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i)+1), -1);
// Perform the watershed algorithm
watershed(src, markers);
Original image:
Result after watershed:
You can find original, intermediate and result image here:
Result images after specific process
In your example, what you consider background is given the same label (5) as the "missing" object.
You can easily adjust this by setting a label (>0) to background, too.
You can find what is for sure background dilating and negating the thresh image.
Then, when creating a marker, you define the labels as:
0: unknown
1: background
>1 : your objects
In your output image, markers will have:
-1 : the edges between objects
0: the background (as intended by watershed)
1: the background (as you defined)
>1 : your objects.
This code should help:
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat3b src = imread("path_to_image");
Mat1b gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat1b thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
// noise removal
Mat1b kernel = getStructuringElement(MORPH_RECT, Size(3,3));
Mat1b opening;
morphologyEx(thresh, opening, MORPH_OPEN, kernel, Point(-1, -1), 2);
Mat1b kernelb = getStructuringElement(MORPH_RECT, Size(21, 21));
Mat1b background;
morphologyEx(thresh, background, MORPH_DILATE, kernelb);
background = ~background;
// Perform the distance transform algorithm
Mat1f dist_transform;
distanceTransform(opening, dist_transform, CV_DIST_L2, 5);
// Normalize the distance image for range = {0.0, 1.0}
// so we can visualize and threshold it
normalize(dist_transform, dist_transform, 0, 1., NORM_MINMAX);
// Threshold to obtain the peaks
// This will be the markers for the foreground objects
Mat1f dist_thresh;
threshold(dist_transform, dist_thresh, 0.5, 1., CV_THRESH_BINARY);
Mat1b dist_8u;
dist_thresh.convertTo(dist_8u, CV_8U);
// Find total markers
vector<vector<Point> > contours;
findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
// Create the marker image for the watershed algorithm
Mat1i markers(dist_thresh.rows, dist_thresh.cols, int(0));
// Background as 1
Mat1i one(markers.rows, markers.cols, int(1));
bitwise_or(one, markers, markers, background);
// Draw the foreground markers (from 2 up)
for (int i = 0; i < int(contours.size()); i++)
drawContours(markers, contours, i, Scalar(i+2), -1);
// Perform the watershed algorithm
Mat3b dbg;
cvtColor(opening, dbg, COLOR_GRAY2BGR);
watershed(dbg, markers);
Mat res;
markers.convertTo(res, CV_8U);
normalize(res, res, 0, 255, NORM_MINMAX);
return 0;
}
Result:
there is very little emgu cv data on watershed.
here is my translation to this problem using c#. i know it is not the right forum but this answer kips popping up so to all the searchers:
//Mat3b src = imread("path_to_image");
//cvtColor(src, gray, COLOR_BGR2GRAY);
Image<Gray, byte> gray = smallImage.Convert<Gray, byte>();
//threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Image<Gray, byte> thresh = gray.ThresholdBinaryInv(new Gray(55), new Gray(255));
// noise removal
Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));
//Mat1b opening;
//morphologyEx(thresh, opening, MORPH_OPEN, kernel, Point(-1, -1), 2);
Image<Gray, byte> opening = thresh.MorphologyEx(MorphOp.Open, kernel, new Point(-1, -1), 2, BorderType.Default, new MCvScalar(255));
//Mat1b kernelb = getStructuringElement(MORPH_RECT, Size(21, 21));
Mat kernel1 = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));
//Mat1b background;
//morphologyEx(thresh, background, MORPH_DILATE, kernelb);
Image<Gray, byte> background = thresh.MorphologyEx(MorphOp.Dilate, kernel, new Point(-1, -1), 2, BorderType.Default, new MCvScalar(255));
background = ~background;
//// Perform the distance transform algorithm
//Mat1f dist_transform;
//distanceTransform(opening, dist_transform, CV_DIST_L2, 5);
Image<Gray, float> dist_transform = new Image<Gray, float>(opening.Width, opening.Height);
CvInvoke.DistanceTransform(opening, dist_transform, null, DistType.L2, 5);
//// Normalize the distance image for range = {0.0, 1.0}
//// so we can visualize and threshold it
//normalize(dist_transform, dist_transform, 0, 1., NORM_MINMAX);
CvInvoke.Normalize(dist_transform, dist_transform, 0, 1.0, NormType.MinMax, DepthType.Default);
//// Threshold to obtain the peaks
//// This will be the markers for the foreground objects
//Mat1f dist_thresh;
//threshold(dist_transform, dist_thresh, 0.5, 1., CV_THRESH_BINARY);
Image<Gray, float> dist_thresh = new Image<Gray, float>(opening.Width, opening.Height);
CvInvoke.Threshold(dist_transform, dist_thresh, 0.5, 1.0, ThresholdType.Binary);
//Mat1b dist_8u;
//dist_thresh.convertTo(dist_8u, CV_8U);
Image<Gray, Byte> dist_8u = dist_thresh.Convert<Gray, Byte>();
//// Find total markers
//vector<vector<Point>> contours;
//findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(dist_8u, contours, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//// Create the marker image for the watershed algorithm
//Mat1i markers(dist_thresh.rows, dist_thresh.cols, int(0));
Image<Gray, int> markers = new Image<Gray, int>(dist_thresh.Width, dist_thresh.Height, new Gray(0));
//// Background as 1
//Mat1i one(markers.rows, markers.cols, int(1));
//bitwise_or(one, markers, markers, background);
Image<Gray, int> one = new Image<Gray, int>(markers.Cols, markers.Rows, new Gray(1));
CvInvoke.BitwiseOr(one, markers, markers, background);
//// Draw the foreground markers (from 2 up)
for (int i = 0; i < contours.Size; i++)
// drawContours(markers, contours, i, Scalar(i + 2), -1);
CvInvoke.DrawContours(markers, contours, i, new MCvScalar(i + 2));
//// Perform the watershed algorithm
//Mat3b dbg;
//cvtColor(opening, dbg, COLOR_GRAY2BGR);
//watershed(dbg, markers);
Image<Bgr, byte> dbg = new Image<Bgr, byte>(markers.Cols, markers.Rows);
CvInvoke.CvtColor(opening, dbg, ColorConversion.Gray2Bgr);
CvInvoke.Watershed(dbg, markers);
//Mat res;
//markers.convertTo(res, CV_8U);
//normalize(res, res, 0, 255, NORM_MINMAX);
CvInvoke.Normalize(markers, markers, 0, 255, NormType.MinMax);
//return 0;
to find light objects on dark background replace ThresholdBinaryInv with ThresholdBinary
I've this image:
EDIT
Sorry but I had to remove the images!
I need to extract the contour of the non-black picture, so I used findcontour with the CV_RETR_EXTERNAL parameter, but I obtain this:
Here's the code:
static Mat canny_output, grey,draw;
vector<vector<Point>> contours;
cvtColor(final_img, grey, CV_BGR2GRAY);
Canny(grey, canny_output, 100, 200);
findContours(canny_output, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
draw = Mat::zeros(canny_output.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++)
{
drawContours(draw, contours, i, Scalar(255, 0, 0));
}
how can I resolve?
Simply add a binarization with minimal threshold, and remove Canny:
cvtColor(final_img, grey, CV_BGR2GRAY);
//Threshold=1: very low value, anyway the rest of the image is pure black
threshold(grey, binary, 1, 255, CV_THRESH_BINARY);
findContours(binary, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);