I am trying to detect the biggest/larger rectangular shape and draw bounding box to the detected area.
In my use case, very often (and not always) the object that represent the rectangle shape is in color white and the background is also in color very similar to white.
Before detecting contours, I have preprocessed the image in order to detect perfect edge.
My problem is that I can't detect edges perfectly and i have a lot of noise even after blurring and using 'adaptive threshold' or 'threshold'.
The original image i have used for contours detection
I have tried different way to detect perfect edge in different lighting condition without success.
How can I process image in order to detect perfect edge (edges with no holes) for contour detection ?
Below is the code i am using
public static Mat findRectangleX(Mat original) {
Mat src = original.clone();
Mat gray = new Mat();
Mat binary = new Mat();
MatOfPoint2f approxCurve;
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
if (original.type() != CvType.CV_8U) {
Imgproc.cvtColor(original, gray, Imgproc.COLOR_BGR2GRAY);
} else {
original.copyTo(gray);
}
Imgproc.GaussianBlur(gray, gray, new Size(5,5),0);
Imgproc.adaptiveThreshold(gray, binary, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY_INV,11, 1);
//Imgproc.threshold(gray, binary,0,255,Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
double maxArea = 0;
Imgproc.findContours(binary, contours, new Mat(),Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i<contours.size();i++) {
MatOfPoint contour = contours.get(i);
MatOfPoint2f temp = new MatOfPoint2f(contour.toArray());
double area = Imgproc.contourArea(contour);
approxCurve = new MatOfPoint2f();
Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.03, true);
if (approxCurve.total() == 4 ) {
Rect rect = Imgproc.boundingRect(contours.get(i));
Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4);
if(maxArea < area)
maxArea = area;
}
}
Log.v(TAG, "Total contours found : " + contours.size());
Log.v(TAG, "Max area :" + maxArea);
return src;
}
I've search similar problems on stackoverflow and try code sample but any of them worked for me. The difficulty i think is the white objet on white background.
How can I process image in order to sharpen the edges for contour detection ?
How can I detect the biggest/larger rectangle shape and draw rectangle line to the detected shape ?
//Updated at : 20/02/2017
i have tried the solution suggested by #Nejc in the post below. The segmentation is better but i still have holes in contour and findcontours fails in detecting the larger contour.
Below is the code provided by #Nejc and translated to java.
public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat saturation = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
Mat imf = new Mat();
saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat abs_x = new Mat();
Core.convertScaleAbs(sobx,abs_x);
Mat abs_y = new Mat();
Core.convertScaleAbs(soby,abs_y);
Core.addWeighted(abs_x, 1, abs_y, 1, 0, grad_abs_val_approx);
sobx.release();
soby.release();
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
/*
Mat thresholded_bin = new Mat();
Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY);
Mat converted = new Mat();
thresholded_bin.convertTo(converted, CV_8UC1);
*/
return thresholded;
}
Here is the image that i have got after running the code above
Image after using #Nejc solution
1) Why my translated code does not output the same image like #Nejc ?
The same code applied to same image should produce the same output ?
2) did i miss something when translating ?
3) For my understanding, why did we multiply the image by itself in this instruction sobx = sobx.mul(sobx); ?
I managed to obtain a pretty nice image of the edge by computing an approximation of the absolute value of gradient of the input image.
EDIT: Before I started working, I resized the input image to 5x smaller size. Click here to see it!. If you use my code on that image, the results will be good. If you want to make my code work well with the image of the original size, then either:
multiply Gaussian kernel sizes and sigmas by 5, or
downsample the image by factor 5, execute the algorithm and then upsample the result by factor 5 (this should work much faster than the first option)
This is the result I got:
My procedure relies on two key features. The first is a conversion to appropriate color space. As Jeru Luke stated in his answer , the saturation channel in HSV color space is the good choice here. The second important thing is the estimation of absolute value of gradient. I used sobel operators and some arithmetics for that purpose. I can provide additional explanations if someone requests them.
This is the code I used to obtain the first image.
using namespace std;
using namespace cv;
Mat img_rgb = imread("letter.jpg");
Mat img_hsv;
cvtColor(img_rgb, img_hsv, CV_BGR2HSV);
vector<Mat> channels_hsv;
split(img_hsv, channels_hsv);
Mat channel_s = channels_hsv[1];
GaussianBlur(channel_s, channel_s, Size(9, 9), 2, 2);
Mat imf;
channel_s.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Mat sobx, soby;
Sobel(imf, sobx, -1, 1, 0);
Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat grad_abs_val_approx;
cv::pow(sobx + soby, 0.5, grad_abs_val_approx);
Mat filtered;
GaussianBlur(grad_abs_val_approx, filtered, Size(9, 9), 2, 2);
Scalar mean, stdev;
meanStdDev(filtered, mean, stdev);
Mat thresholded;
cv::threshold(filtered, thresholded, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_TOZERO);
// I scale the image at this point so that it is displayed properly
imshow("image", thresholded/50);
And this is how I computed the second image:
Mat thresholded_bin;
cv::threshold(filtered, thresholded_bin, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_BINARY);
Mat converted;
thresholded_bin.convertTo(converted, CV_8UC1);
vector<vector<Point>> contours;
findContours(converted, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
Mat contour_img = Mat::zeros(converted.size(), CV_8UC1);
drawContours(contour_img, contours, -1, 255);
imshow("contours", contour_img);
Thanks for yours comments and suggestion.
The code provided by #NEJC works perfectly and cover 80% of my use case.
Nevertheless, it does not works with similar case like this
case not solved by the current code
and i don't know why.
Perhaps someone have an idea/clue/solution ?
I continue to improve the code and try to find a more generic solution that can cover more case. I will post it if i ever i find.
In any case, below is the working code based on #NEJC solution and notes.
public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat saturation = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
Mat imf = new Mat();
saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat sumxy = new Mat();
Core.add(sobx,soby, sumxy);
Core.pow(sumxy, 0.5, grad_abs_val_approx);
sobx.release();
soby.release();
sumxy.release();;
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
/*
Mat thresholded_bin = new Mat();
Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY_INV);
Mat converted = new Mat();
thresholded_bin.convertTo(converted, CV_8UC1);
*/
Mat converted = new Mat();
thresholded.convertTo(converted, CV_8UC1);
return converted;
}
Related
From the following image, how could I find the result image?
The images shown here are threshold images. I have tried using morphological operators but they even remove the blob I want. How could I solve this problem?
Any hints?
Following is the result image I am interested to get/find:
import cv2
diff = cv2.imread('Image.png',0)
ret, thresh = cv2.threshold(diff, 12.5, 255, cv2.THRESH_BINARY)
thresh = cv2.dilate(thresh, None, iterations = 1)
cv2.imshow('img', thresh) # This is the first picture I have shown
cv2.waitKey(0)
You are most of the way there, all you need to do now is find the blobs, add some contours and find the biggest one. Easy! below is the code in C++, ill leave it up to you to work out how to convert it to Python:
cv::Mat mat = imread("g0cVU.png");
Mat origImage = mat;
Mat canny_output = mat;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
cv::Mat greyMat, colorMat;
cv::cvtColor(mat, greyMat, CV_BGR2GRAY);
int thresh = 100;
RNG rng(12345);
///// Detect edges using canny
Canny(greyMat, canny_output, thresh, thresh * 2, 3);
/// Find contours
findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
int largest_area = 0;
int largest_contour_index = 0;
Rect bounding_rect;
/// Draw contours
Mat drawing = Mat::zeros(canny_output.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());
double a=contourArea( contours[i],false); // Find the area of contour
if(a>largest_area){
largest_area=a;
largest_contour_index=i; //Store the index of largest contour
bounding_rect=boundingRect(contours[i]); // Find the bounding rectangle for biggest contour
}
}
rectangle(origImage, bounding_rect, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),2);
/// Show in a window
namedWindow("Contours", CV_WINDOW_AUTOSIZE);
imshow("Contours", drawing);
cv::namedWindow("img");
cv::imshow("mat", mat);
cv::imshow("mat", origImage);
cv::imshow("mat123", drawing);
cv::waitKey(0);
Which gives this results:
You can see in the bottom image the largest contor has a brown rectangle drawn around it.
o and obviously once you have the largest blob (or whatever blob you deem "the correct one") you can just set everything else to black which is fairly straightforward.
I have an image and 2 regions (focus region and de-focus region). I use Open CV, I want to detect near region.
I apply watershed in OpenCV or Canny detector to detect the object. But the object includes near and far region.
So, I need an idea or help from anyone help me apply OpenCV to detect near region image.
Code for show image, that I attached.
private Mat CalculateMapStrength(Mat inputMat){
Imgproc.cvtColor(inputMat,inputMat, Imgproc.COLOR_RGBA2GRAY);
//Compute dx and dy derivatives
Mat dx = new Mat();
Mat dy = new Mat();
Imgproc.Sobel(inputMat, dx, CV_32F, 1, 0);
Imgproc.Sobel(inputMat, dy, CV_32F, 0, 1);
Core.convertScaleAbs(dx,dx);
Core.convertScaleAbs(dy,dy);
Mat outputMat = new Mat();
Core.addWeighted(dx,0.5,dy,0.5,0,outputMat);
return outputMat;
}
Beside, I get Image segmentation by watershed algorithm in OpenCV. Can I compile 2 result for detect object? How to compile that?
public Mat steptowatershed(Mat img)
{
Mat threeChannel = new Mat();
Imgproc.cvtColor(img, threeChannel, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(threeChannel, threeChannel, 100, 255, Imgproc.THRESH_BINARY);
Mat fg = new Mat(img.size(),CvType.CV_8U);
Imgproc.erode(threeChannel,fg,new Mat());
Mat bg = new Mat(img.size(),CvType.CV_8U);
Imgproc.dilate(threeChannel,bg,new Mat());
Imgproc.threshold(bg,bg,1, 128,Imgproc.THRESH_BINARY_INV);
Mat markers = new Mat(img.size(),CvType.CV_8U, new Scalar(0));
Core.add(fg, bg, markers);
Mat result1=new Mat();
WatershedSegmenter segmenter = new WatershedSegmenter();
segmenter.setMarkers(markers);
Imgproc.cvtColor(img, img, Imgproc.COLOR_RGBA2RGB);
result1 = segmenter.process(img);
return result1;
}
public class WatershedSegmenter
{
public Mat markers=new Mat();
public void setMarkers(Mat markerImage)
{
markerImage.convertTo(markers, CvType.CV_32SC1);
}
public Mat process(Mat image)
{
Imgproc.watershed(image,markers);
markers.convertTo(markers,CvType.CV_8U);
return markers;
}
}
i am working on project where i need to detect the contour of differents type of document.
Currently i am able to segment and detect contours using findcontours and everything works fine in most cases.
But, if the document is white color and the background similar to white color, i can't detect the contour.
For example, in this image
or this xhttp://i.stack.imgur.com/9exrg.jpg
i can't detect the white paper.
Here is the code i am using to segment the image in order to detect perfect edge (edges with no holes) / Perfectly straight.
public static Mat process(Mat original){
Mat src = original.clone();
Mat hsvMat = new Mat();
Mat gray = new Mat();
Mat sobx = new Mat();
Mat soby = new Mat();
Mat grad_abs_val_approx = new Mat();
Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
List<Mat> hsv_channels = new ArrayList<Mat>(3);
Core.split(hsvMat, hsv_channels);
Mat hue = hsv_channels.get( 0 );
Mat sat = hsv_channels.get( 1 );
Mat val = hsv_channels.get( 2 );
Imgproc.GaussianBlur(val, gray, new Size(9, 9), 2, 2);
Mat imf = new Mat();
gray.convertTo(imf, CV_32FC1, 0.5f, 0.5f);
Imgproc.Sobel(imf, sobx, -1, 1, 0);
Imgproc.Sobel(imf, soby, -1, 0, 1);
sobx = sobx.mul(sobx);
soby = soby.mul(soby);
Mat sumxy = new Mat();
Core.add(sobx,soby, sumxy);
Core.pow(sumxy, 0.5, grad_abs_val_approx);
sobx.release();
soby.release();
sumxy.release();;
Mat filtered = new Mat();
Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);
final MatOfDouble mean = new MatOfDouble();
final MatOfDouble stdev = new MatOfDouble();
Core.meanStdDev(filtered, mean, stdev);
Mat thresholded = new Mat();
Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);
Mat converted = new Mat();
thresholded.convertTo(converted, CV_8UC1);
return converted;
}
Using the code above leads to the following result :
As you can notice, the edges is not Perfectly straight (and there is holes). Edges are barely visible and Findcontours fails to detect the contours.
I have tried alls solutions/suggestions described here
therefore, here is my questions :
1) what's wrong with my code ?
2) how can i preprocess the image in order to detect perfect edge (edges with no holes) / Perfectly straight for contour detection ?
Many thanks in advance for your assistance.
When you see that the edges are not easily detected, you can try other complementary approaches.
For example, on this image, the simple matlab code (which you can implement using OpenCV):
I=imread('page.png');
r=I(:,:,1);
g=I(:,:,2);
b=I(:,:,3);
imshow(b>g);
Produces the following result, which you can use using your edge detection code:
I need some help with opencv and gearwheel detection.
My task: count gearwheel teeth from images like this:
Im trying to use HoughCircles method but got bad results lile this:
Otsu threshold:
Code (on openCV Java wrapper):
Mat des = new Mat(sourceImg.rows(), sourceImg.cols(), sourceImg.type());
Imgproc.cvtColor(sourceImg, sourceImg, Imgproc.COLOR_BGR2GRAY, 4);
Imgproc.GaussianBlur(sourceImg,des, new Size(3,3),0,0);
Imgproc.threshold(des, des, 0, 255, Imgproc.THRESH_OTSU | Imgproc.THRESH_OTSU);
Imgproc.Canny(des, des, 0 , 1);
displayImage(Mat2BufferedImage(des));
Mat circles = new Mat();
Imgproc.HoughCircles(des, circles, Imgproc.CV_HOUGH_GRADIENT, 1.0, 50, 70.0, 30.0, 100, 0);
/// Draw the circles detected
for(int i = 0; i < circles.cols(); i++ )
{
double vCircle[] = circles.get(0,i);
if (vCircle == null)
break;
Point pt = new Point(Math.round(vCircle[0]), Math.round(vCircle[1]));
int radius = (int)Math.round(vCircle[2]);
// draw the found circle
Core.circle(des, pt, radius, new Scalar(255,255,255), 3);
Core.circle(des, pt, 3, new Scalar(255,0,0), 3);
}
What is right way for my task? How to count teeth? Thanks for your answers.
Here's what I tried. The code is in C++ but you can easily adapt it to Java.
load the image and resize it to half the size
erode the image, use Canny to detect edges, then dilate to connect the edges
find contours and choose the largest contour
find the convexhull of this largest contour. Number of point in the convexhull will give you a rough value for the number of teeth
Here's the largest contour and the convexhull points:
I get a value of 77 with the following code.
Mat gray = imread("16atchc.jpg", 0);
Mat small, bw, er, kernel;
resize(gray, small, Size(), .5, .5);
kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
erode(small, er, kernel);
Canny(er, bw, 50, 150);
dilate(bw, bw, kernel);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
int imax = 0, areamax = 0;
findContours(bw, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
Rect rect = boundingRect(contours[idx]);
int area = rect.width * rect.height;
if (area > areamax)
{
areamax = area;
imax = idx;
}
}
vector<Point> hull;
convexHull(contours[imax], hull);
cout << contours[imax].size() << ", " << hull.size() << endl;
I'm trying to achieve background subtraction in openCV 2.2 using the cv namespace (Qt4.7). I have the following code which compiles fine but when running the program breaks because one mat doesn't equal the other but I can't find out where it is and I'm currently going through the API reference to try and find it.
cvtColor( mcolImage, mcolImage, CV_BGR2RGB);
cvtColor( mcolImage, gscaleImage, CV_RGB2GRAY);
acc = Mat(Size(440,320), CV_32FC3);
accSQ = Mat(Size(440,320), CV_32FC3);
//we accumulate into a Mat to get an frames average
Mat avg;
accumulateWeighted(gscaleImage, acc, 3.0, Mat());
accumulateSquare(gscaleImage, accSQ, Mat());
multiply(acc, acc, avg, 1);
Mat sigma, sigmaSQRT;
subtract(accSQ, avg, sigmaSQRT, Mat());
sqrt(sigmaSQRT, sigma); //Holds the standard deviation
Mat fgImage; //hold the foreground image
cv::absdiff(avg,gscaleImage, fgImage);
//GaussianBlur(gscaleImage, gscaleImage, Size(7,7), 2, 2 );
Mat buff ;
//convert to black and white
threshold(fgImage, buff, 75, THRESH_BINARY, 100);
dilate(buff, buff, Mat(3, 3, CV_8UC1), Point(-1, -1), 1, BORDER_CONSTANT, Scalar(1.0, 1.0, 1.0, 0));
erode(buff, buff, Mat(3, 3, CV_8UC1), Point(-1, -1), 1, BORDER_CONSTANT, Scalar(1.0, 1.0, 1.0, 0));
//rectangle(gscaleImage, cvPoint(100, 300), cvPoint(200, 100), cvScalar(255, 255, 255, 0), 1);
QImage colImagetmp((uchar*)mcolImage.data, mcolImage.cols, mcolImage.rows, mcolImage.step,
QImage::Format_RGB888 ); //Colour
QImage gscaleImagetmp ((uchar*)gscaleImage.data, gscaleImage.cols, gscaleImage.rows, gscaleImage.step,
QImage::Format_Indexed8); //Greyscale. I hope
QImage bwImagetmp((uchar*)buff.data, buff.cols, buff.rows, buff.step,
QImage::Format_Indexed8);
//Setup a colour table for the greyscale image
QVector<QRgb> colorTable;
for (int i = 0; i < 256; i++) colorTable.push_back(qRgb(i, i, i));
bwImagetmp.setColorTable(colorTable);
gscaleImagetmp.setColorTable(colorTable);
ui.intDisplay->setPixmap(QPixmap::fromImage(bwImagetmp));
ui.bwDisplay->setPixmap(QPixmap::fromImage(gscaleImagetmp));
ui.colDisplay->setPixmap( QPixmap::fromImage(colImagetmp ));
Thanks for the help in advanced.
Edit:
After going through the code I found that the absdiff(avg, gscaleImage, fgImage); is where the program is crashing. I think it maybe crashing on the second parameter but not sure.
I solved it (I think) by declaring a new temporary Mat and converting that specifically (using avg.convert() ) to match the gscaleImage type and size.