I want to get all the outer contours with RETR_EXTERNAL but for some weird reason openCV thinks that the image border is a contour too and therefore discards all inner contours. What exactly am I doing wrong here?
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(imageA, contours, hierarchy, Imgproc.RETR_EXTERNAL,
Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++) {
double[] c = hierarchy.get(0, i);
Rect rect = Imgproc.boundingRect(contours.get(i));
Core.rectangle(image, new Point(rect.x, rect.y),
new Point(rect.x + rect.width, rect.y + rect.height),
new Scalar(0, 255, 0), 3);
}
Input (imageA was processed to this before contour-finding):
Output:
EDIT:
Problem partially solved
Inverting the pixels so that black is the background and white the foreground helped with the image above image. However I still get inner contours on some images. Like this one:
Input
Output
Your input image isnt good enought o extract the contours you want to have.
Your input contours are these (part of your image):
each color is a single contour (and some of the white ones)
For the red contour I've drawn the bounding rectangle which is the same method that you used to display the contours. All the other colored contours aren't inside of the red contour, but just inside of the bounding rectangle, that's why they are found even though you selected to only find the outer contours.
What you really want is something like this:
but to get that result, your input image must have that lines of the ellipse connected, too!!
For your input image it will be very hard to extract those lines, without getting lines of the ground too, but an easy approach could be to use a couple of dilation operations followed by the same number of erosion operations on your input image, before extracting contours. This won't be stable for all setting though ;)
Related
I'm working on android app, which determines which font is used on a text image. So I need to extract every character from image and don't know how to do it precisely. Furthermore, when I'm trying to process an image I have one result...but my classmate has different (for example, more or less noise). The problem with character detection is that:
1) it detects also some noise blobs on image and shows it in rectangles (I thought about detectMultiScale... but I have doubts about it, maybe there are easiest ways to detect characters)
2) it detects several contours of one character (for example inner and outer radius of letter "o")
And question for the future: I'm going to create a DB with images (for now just 3 fonts) of different letters of fonts and compare them with an image of letters from photo. Maybe someone could recommend a better way to do it.
So this is part of code with image processing(I'm still playing with values of blur, threshold and Canny... but there was no really positive result):
Imgproc.cvtColor(sImage, grayImage, Imgproc.COLOR_BGR2GRAY); //градации серого
Imgproc.GaussianBlur(grayImage,blurImage,new Size(5, 5),0); //размытие
Imgproc.adaptiveThreshold(blurImage, thresImage, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 101, 39);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.Canny(thresImage, binImage, 30, 10, 3, true); //контур
Imgproc.findContours(binImage, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0));
hierarchy.release();
Imgproc.drawContours(binImage, contours, -1, new Scalar(255, 255, 255));//, 2, 8, hierarchy, 0, new Point());
MatOfPoint2f approxCurve = new MatOfPoint2f();
//For each contour found
for (int i = 0; i < contours.size(); i++) {
//Convert contours(i) from MatOfPoint to MatOfPoint2f
MatOfPoint2f contour2f = new MatOfPoint2f(contours.get(i).toArray());
//Processing on mMOP2f1 which is in type MatOfPoint2f
double approxDistance = Imgproc.arcLength(contour2f, true) * 0.02;
Imgproc.approxPolyDP(contour2f, approxCurve, approxDistance, true);
//Convert back to MatOfPoint
MatOfPoint points = new MatOfPoint(approxCurve.toArray());
// Get bounding rect of contour
Rect rect = Imgproc.boundingRect(points);
// draw enclosing rectangle (all same color, but you could use variable i to make them unique)
Imgproc.rectangle(binImage, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(255, 255, 255), 5);
}
And screen (not actually with processing values from code, just one with better results):
Original:
(unfortunately, I can't add more than 2 links to show more examples)
There were situations, when picture from this screen looked pretty good, but another pictures looked like with shapeless blobs.
Your code is fine, you just need to make a minor tweaks to get it work properly.
Firstly, the image size is very large, you can safely reduce it to 20% of current size without suffering a major loss in accuracy. Due to larger image size all the functions would perform slower.
You dont need to perform adaptive threshold before Canny, canny works perfectly on gray-scale images as well, You need to adjust the params as:
Canny(img, threshold1=170, threshold2=250)
which yields an image as:
[Optional] If you want to de-noise the image then you can try with morphological operations like erode and dilate.
Now you are ready to find the contours. The mistake in your code was using Imgproc.RETR_TREE flag you need to use Imgproc.RETR_EXTERNAL flag to get only the outer contours and not the nested inner contours.
At this step you may have some unwanted small contours, which can be filtered as:
// ** Below code if for reference purposes only, consult OpenCV docs for proper API methods
int character_area_lower_thresh = 10;
for (Contour c:contours) {
if (Imgproc.contourArea(c) > character_area_lower_thresh) {
// Desired contour, do what ever you want to do
Rect r = Imgproc.boundingRect(c);
}
}
I am working on a Rubik's side scanner to determine what state the cube is in. I am quite new to computer vision and using it so it has been a little bit of a challenge. What I have done so far is that I use a video capture and at certain frames capture that frame and save it for image processing. Here is what it looks like.
When the photo is taken the cube is in the same position each time so I don't have to worry about locating the stickers.
What I am having trouble doing is getting a small range of pixels in each square to determine its HSV.
I know the ranges of HSV are roughly
Red = Hue(0...9) AND Hue(151..180)
Orange = Hue(10...15)
Yellow = Hue(16..45)
Green = Hue(46..100)
Blue = Hue(101..150)
White = Saturation(0..20) AND Value(230..255)
So after I have captured the image I then load it and split the HSV values of the image but don't know how to get the certain pixel coordinates of the image. How do I do so?
BufferedImage getOneFrame() {
currFrame++;
//At the 90th frame I capture that frame and save that frame
if (currFrame == 120) {
cap.read(mat2Img.mat);
mat2Img.getImage(mat2Img.mat);
Imgcodecs.imwrite("firstImage.png", mat2Img.mat);
}
cap.read(mat2Img.mat);
return mat2Img.getImage(mat2Img.mat);
}
public void splitChannels() {
IplImage firstShot = cvLoadImage("firstImage.png");
//I split the channels so that I can determine the value of the pixel range
IplImage hsv = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), firstShot.nChannels());
IplImage hue = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), CV_8UC1 );
IplImage sat = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), CV_8UC1 );
IplImage val = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), CV_8UC1 );
cvSplit( hsv, hue, sat, val, null );
//How do I get a small range of pixels of my images to determine get their HSV?
}
If I understand your question right, you know the coordinates of all areas that interest you. Save the information about each area into cvRect objects.
You can traverse the rectangle area by looping. Make a double loop. In outer loop start at rect.y and stop before rect.y + rect.height. In inner loop, do a similar thing in x direction. Inside the loop, use CV_IMAGE_ELEM macro to access individual pixel values and compute whatever you need.
One advice though: There are several advantages to using Mat instead of IplImage when working with OpenCV. I recommend that you start using 'Mat', unless you have some special reasons to do so, of course. Click to see the documentation and take a look at one of constructors that takes one Mat and one Rect as parameters. This constructor is your good friend - you can create a new Mat object (without copying any data) which will only contain the area inside the rectangle.
I wanna make an ellipse mask for cropping an image so only the contents inside of the ellipse will be shown.
Could you inspect my code?
public static Mat cropImage(Mat imageOrig, MatOfPoint contour){
Rect rect = Imgproc.boundingRect(contour);
MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
RotatedRect boundElps = Imgproc.fitEllipse(contour2f);
Mat out = imageOrig.submat(rect);
// the line function is working
Imgproc.line(out, new Point(0,0), new Point(out.width(), out.height()), new Scalar(0,0,255), 5);
// but not this one
Imgproc.ellipse(out, boundElps, new Scalar(255, 0, 0), 99);
return out;
}//cropImage
It seems like it's not working at all. Though you can see the line function I've done to test if it is working on the right image and I can see a line but there's no ellipse.
Here's a sample output of my cropImage function.
TIA
You're retrieving the ellipse coordinates in the imageOrig coordinates system, but you're showing it on the cropped out image.
If you want to show the ellipse on the crop, you need to translate the ellipse center to account for the translation introduced by the crop (top-left coordinates of rect), something like:
boundElps.center().x -= rect.x; boundElps.center().y -= rect.y;
You can try this out:
RotatedRect rRect = Imgproc.minAreaRect(contour2f);
Imgproc.ellipse(out, rRect , new Scalar(255, 0, 0), 3);
You should check for the minimum requirements for using the fitEllipse as shown in this post.
The function fitEllipse requires atleast 5 points.
Note: Although the reference I mention is for Python, I hope you can do the same for Java.
for cnt in contours:
area = cv2.contourArea(cnt)
# Probably this can help but not required
if area < 2000 or area > 4000:
continue
# This is the check I'm referring to
if len(cnt) < 5:
continue
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(roi, ellipse, (0, 255, 0), 2)
Hope it helps!
In my android app I get an image from the gallery as a bitmap with something like this
Bitmap bitm = getMyImage("Thanks!");
and I have a Mat called mat declared like this:
Mat mat = new Mat(bitm.getHeight(), bitm.getWidth(), CVType.CV_8UC3);
I'm trying to get contour areas from the image, which I've successfully gotten then draw it back on the original image with:
Imgproc.drawContours(mat, contours, -1, new Scalar(200,200,0), 2);
displayMat(mat);
If I use it like that, it works but the contours are drawn on a blank image which isn't what I want. I want it to be drawn on the original image. If I use
Utils.bitmapToMat(bitm, mat);
before the previous snippet of code, the displayed image is just the preprossed image without the 'Drawn' contours. Why?
Asfaik Android uses images with alpha values, so CV_8UC4 is the right data type.
So
Mat mat = new Mat(bitm.getHeight(), bitm.getWidth(), CVType.CV_8UC3);
Imgproc.drawContours(mat, contours, -1, new Scalar(200,200,0), 2);
displayMat(mat);
draws the contours correctly on an empty/blank 8UC3 image (if memory empty).
But if you want to draw on the input image by first converting Utils.bitmapToMat(bitm, mat); you'll overwrite your 8UC3 memory and replace it by 8UC4 data. After that you draw Scalar(200,200,0) which will use a 4th channel, but cv::Scalar automatically adds those channels with default zero values, so you draw your contours in transparent. So use Scalar(200,200,0,255) instead and it should give your expected results.
Mat mat = new Mat(bitm.getHeight(), bitm.getWidth(), CVType.CV_8UC3);
Utils.bitmapToMat(bitm, mat);
Imgproc.drawContours(mat, contours, -1, new Scalar(200,200,0,255), 2);
displayMat(mat);
The other method would be to convert the bitmap to 8UC3, but I'm not sure how to do that.
I am developing an Android app that calculates the sum of all points of the being-seen dominoes pieces -shown in picture- using OpenCV for Android.
The problem is, I can't find a way to filtering other contours and counting only dots I see in the dominoes, I tried to use Canny edge finding then use HoughCircles, but with no result, as I don't have an absolute top view of the rocks and HoughCircles detect perfect circles only :)
Here is my code:
public Mat onCameraFrame(Mat inputFrame) {
inputFrame.copyTo(mRgba);
Mat grey = new Mat();
// Make it greyscale
Imgproc.cvtColor(mRgba, grey, Imgproc.COLOR_RGBA2GRAY);
// init contours arraylist
List<MatOfPoint> contours = new ArrayList<MatOfPoint>(200);
//blur
Imgproc.GaussianBlur(grey, grey, new Size(9,9), 10);
Imgproc.threshold(grey, grey, 80, 150, Imgproc.THRESH_BINARY);
// because findContours modifies the image I back it up
Mat greyCopy = new Mat();
grey.copyTo(greyCopy);
Imgproc.findContours(greyCopy, contours, new Mat(), Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);
// Now I have my controus pefectly
MatOfPoint2f mMOP2f1 = new MatOfPoint2f();
//a list for only selected contours
List<MatOfPoint> SelectedContours = new ArrayList<MatOfPoint>(400);
for(int i=0;i<contours.size();i++)
{
if(here I should put a condition that distinguishes my spots, eg: if contour inside is black and is a black disk)
{
SelectedContours.add(contours.get(i));
}
}
Imgproc.drawContours(mRgba, SelectedContours, -1, new Scalar(255,0,0,255), 1);
return mRgba;
}
EDIT:
One unique feature of my contours after threshold is they're totally black from inside, is there anyway I could calculate the mean color/intensity for a given contour ?
There is a similiar problem and possible solution on SO, titled Detection of coins (and fit ellipses) on an image. Here you will find some recomendations about opencv's function fitEllipse.
You should take a look at this for more info on opencv's function fitEllipse.
Also, to detect only black elements in an image, you can use HSV color model, to find only black colors. You can find an explanation here.