I want to identify and extract the contour of the largest leaf of the following image using OpenCV and C++.
I applied Canny edge detector to the image and got the following result.
Canny(img_src, img_edge_detected, 20, 60, 3);
Now I want to extract the largest contour (largest leaf) form the image and draw the contour line, but the problem here is the edge line of the largest leaf is not continuous. So I looked in to dialate and morphological close but using those functions I couldn't get a good result to extract the area. Is there any way to get the largest contour in such image?
Note that here I cannot use template matching or any masking kind of things because my final intention is to built a system where a user can upload an image and get the species of the plant. So the system doesn't have any prior idea about the shape of the leaf that user is going to upload.
Please tell me how to find and draw the largest contour here if it is possible.
Thanks.
cant you use hsv color threshoding to track only that leaf and then you can straight away use minmaxloc function to get the area of the largest contour.just an idea try doing it like that.it will work.good luck
Same thing i will do in java please convert it into c++, here BGR to convert HSV then after apply the combination of the yellow, green and brown with specified range and simply perfom bitwise or operation. it will be give to you not zero pixles using opencv function Core.findNonZero(Mat src, Mat dst);
Imgproc.cvtColor(mRgba, mHSV, Imgproc.COLOR_BGR2HSV, 4);
//Yellow
Core.inRange(mHSV, new Scalar(25, 80, 80), new Scalar(36, 255, 255), yellow);
//Green
Core.inRange(mHSV, new Scalar(37, 80, 80), new Scalar(70, 255, 255), green);
//Brown
Core.inRange(mHSV, new Scalar(10, 80, 80), new Scalar(30, 200, 200), brown);
// logical OR mask
Core.bitwise_or(yellow, green, green);
Core.bitwise_or(green, brown, mask);
Imgproc.dilate(mask, mask, new Mat());
// Find non zero pixels
pts = Mat.zeros(mask.size(), mask.type());
Core.findNonZero(mask, pts);
return mask;
Related
I am aiming to develop an iOS app that captures an image > extracts Braille dots represented in a specific color (Blue) > translates Braille letters into text using image processing techniques.
My approach was to use OpenCV/C++ to have the image be processed to extract the blue colored dots as shown in this photo into this photo
The next step is to recognize Braille in the image to translate it into text, one solution was to put a grid on the image to find the intersection points pixel color value then classify them into (1 if white , 0 if black) as represented in this photo
The problem with the proposed solution was:
how to position the grid rows/columns at the wanted positions?
how to get the coordinates and value(0 or 1)of the intersection points?
If you have any suggestion/solution about the proposed solution or any other solution please share them It would be appreciated since I don't have an experience in OpenCV/C++ field.
*note that python solutions cannot be used in iOS (as far as I know).
I attached my code for reference
+ (UIImage *)detectRedShapesInImage:(UIImage *)image{
cv::Mat mat;
UIImageToMat(image, mat);
cv::medianBlur(mat, mat, 3);
// Convert input image to HSV
cv::Mat hsv_image;
cv::cvtColor(mat, hsv_image, cv::COLOR_BGR2HSV);
// Threshold the HSV image, keep only the red (replaced it with blue) pixels
cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);
// Combine the above two images
cv::Mat red_hue_image;
cv::addWeighted(lower_red_hue_range, 1.0, upper_red_hue_range, 1.0, 0.0, red_hue_image);
cv::GaussianBlur(red_hue_image, red_hue_image, cv::Size(9, 9), 2, 2);
// detect circules, for now it doesnot take all circles
std::vector<cv::Vec4f> circles;
cv::HoughCircles(red_hue_image, circles, cv::HOUGH_GRADIENT, 1.0, 20, 150, 40, 0, 0);
// Loop over all detected circles and outline them on the original image
if(circles.size() == 0) std::exit(-1);
for(size_t current_circle = 0; current_circle < circles.size(); ++current_circle) {
cv::Point center(std::round(circles[current_circle][0]), std::round(circles[current_circle][1]));
int radius = std::round(circles[current_circle][2]);
cv::circle(red_hue_image, center, radius, cv::Scalar(0, 255, 0), 5);
}
UIImage *maskedShapesImg = MatToUIImage(red_hue_image);
return maskedShapesImg;}
Hints:
Assuming that the text is fairly horizontal and the spacing between lines is sufficient:
get the centers of the dots;
find the shortest horizontal and vertical distances between dots; these give you the horizontal and vertical pitch;
cluster the dots that are no more than one horizontal or two vertical pitches apart (with a safety margin); a cluster should correspond to a single character;
find the top-left corner of the characters;
find the median horizontal and vertical distance between the characters;
from this information (dot and character spacing), map the dots to the grid by predicting the grid nodes and using the nearest-neighbor rule.
This work is made a little tricky by the fact that not all character occupy the two columns and three rows of dots.
I got a problem where I need to access pixels of a opencv Mat image container.
I use opencv inRange function to create a mask. In that mask I need to check the value of different pixels, but I won't receive the values I expect to receive.
// convert image to hsv for better color-detection
cv::Mat img_hsv, maskR, maskY, mask1, mask2;
cv::cvtColor(image, img_hsv, cv::COLOR_BGR2HSV);
// Gen lower mask (0-5) and upper mask (175-180) of RED
cv::inRange(img_hsv, cv::Scalar(0, 50, 20), cv::Scalar(5, 255, 255), mask1);
cv::inRange(img_hsv, cv::Scalar(175, 50, 20), cv::Scalar(180, 255, 255), mask2);
// Merge the masks
cv::bitwise_or(mask1, mask2, maskR);
after that I try to read the pixel values where I got extremely high values and even nans, but most of them zeros, which is expected as the mask is only black and white
if (maskR.at<double>(position.x, position.y) == 255)
is there something I'm missing? I tried with double, uchar, int and float
when I print the mask, I can clearly see the 0 and 255 entries(no nans or strange numbers), but when I access them with the at() function, I wont get the same results.
The coordinates of the pixels should be in the range of the Mat as the dimension of the mask is 1080x1920 and non of the coordinates reach over that.
I got the dimension by using cv::size
I finally found the answer to my own question.
It works when I use uchar:
maskR.at<uchar>(position.x, position.y) == 255
I thought this wouldn't work because printing this with std::cout wouldn't give me an output, but the reason for that is that I forgot to cast uchar so it could be printed in the console
I have a camera under a glass with IR light to detect objects. I can find the contours and draw them using the following code (I just found some examples online and modified it to my need so I am not a master at all!).
using namespace cv;
cvtColor(mat, mat, COLOR_BGR2GRAY);
blur(mat, mat, Size(3,3));
erode(mat, mat, NULL, Point(-1,-1), 2);
dilate(mat, mat, NULL, Point(-1,-1), 2);
Canny(mat, mat, 100, 200);
auto contours = std::vector<std::vector<Point>>();
auto hierarchy = std::vector<Vec4i>();
findContours(mat, contours, hierarchy, CV_RETR_TREE,
CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat drawing = Mat::zeros(mat.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());
}
putText(mat,
(QString("Blobs: %1").arg(contours.size())).toStdString(),
Point(25,175), cv::FONT_HERSHEY_PLAIN, 10, CV_RGB(0, 0, 255), 2);
This code results in a nice finding of the contours that I am quite happy with. Except the fact that my IR light somehow makes artifacts at the corners and bottom of the image.
You can see that I have used gimp to highlight the areas that I want to ignore while searching for contours. Under the gray shade you see the white pixels that my original code detects as contours. These areas are problematic and I want to exclude them from the either contour search or contour drawing (whichever is easier!)
I was thinking of cropping the image to get the ROI but the cropping is a rectangle while I (for example) could have things to be detected i.e. exactly at leftmost area.
I think there should be some data in the contour that tells me where are the pixels but I could not figure it out yet...
The easiest way would be to simply crop the image. Areas of the image are known as ROIs in OpenCV, which stands for Region of Interest.
So, you could simply say
cv::Mat image_roi = image(cv::Rect(x, y, w, h));
This basically makes a rectangular crop, with the top left corner at x,y, width w and height h.
Now, you might not want to reduce the size of the image. The next easiest way is to remove the artifacts is to set the borders to 0. Using ROIs, of course:
image(cv::Rect(x, y, w, h)).setTo(cv::Scalar(0, 0, 0));
This sets a rectangular region to black. You then have to define the 4 rectangular regions on the borders of your image that you want dark.
Note that all of the above is based on manual tuning and some experimentation, and it would work provided that your system is static.
I have an image of a circle, I want to find the circle but not using hough circles.
I found a way, linked here.
But I can't find the transition coordinates from white to black as I don't know the x and y coordinates in the circle. What other methods are there, or how can I make that approach work?
This is my test image:
Another approach (that is useful for more than just circles) would be to find the image contours and do image moment analysis on the circle to find it's centre of mass:
I recommend learning them if you'e going to move forward with image processing. They're pretty helpful approaches that transform images into more useful structures.
One possible approach is to first threshold the image to get rid of some of the noise around the circle. Then you can extract the edge of the circle using Canny edge detection. Finally, findNonZero to get a list of pixel coordinates.
I first did a quick prototype with Python:
import cv2
import numpy as np
img = cv2.imread('circle.png', 0)
mask = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)[1]
edges = cv2.Canny(mask, 20, 100)
points = np.array([p[0] for p in cv2.findNonZero(edges)])
And then ported it to C++, adding some extra code to save all the intermediate images and plot the found pixels.
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat img(cv::imread("circle.png", 0));
cv::Mat mask;
cv::threshold(img, mask, 127, 255, cv::THRESH_BINARY);
cv::imwrite("circle_1.png", mask);
cv::Mat edges;
cv::Canny(mask, edges, 20, 100);
cv::imwrite("circle_2.png", edges);
std::vector<cv::Point2i> points;
cv::findNonZero(edges, points);
cv::Mat output(cv::Mat::zeros(edges.size(), CV_8UC3));
for (auto const& p : points) {
output.at<cv::Vec3b>(p) = cv::Vec3b(127, 255, 127);
}
cv::imwrite("circle_3.png", output);
}
Output of threshold:
Output of Canny:
Re-plotted pixels:
I'm trying to threshold red pixels in a video stream using OpenCV. I have other colors working quite nicely, but red poses a problem because it wraps around the hue axis (ie. HSV(0, 255, 255) and HSV(179, 255, 255) are both red). The technique I'm using now is less than ideal. Basically:
cvInRangeS(src, cvScalar(0, 135, 135), cvScalar(20, 255, 255), dstA);
cvInRangeS(src, cvScalar(159, 135, 135), cvScalar(179, 255, 255), dstB);
cvOr(dstA, dstB, dst);
This is suboptimal because it requires a branch in the code for red (potential bugs), the allocation of two extra images, and two extra operations when compared to the easy case of blue:
cvInRangeS(src, cvScalar(100, 135, 135), cvScalar(140, 255, 255), dst);
The nicer alternative that occurred to me was to "rotate" the image's colors, so that the target hue is at 90 degrees. Eg.
int rotation = 90 - 179; // 179 = red
cvAddS(src, cvScalar(rotation, 0, 0), dst1);
cvInRangeS(dst1, cvScalar(70, 135, 135), cvScalar(110, 255, 255), dst);
This allows me to treat all colors similarly.
However, the cvAddS operation doesn't wrap the hue values back to 180 when they go below 0, so you lose data. I looked at converting the image to CvMat so that I could subtract from it and then use modulus to wrap the negative values back to the top of the range, but CvMat doesn't seem to support modulus. Of course, I could iterate over every pixel, but I'm concerned that that's going to be very slow.
I've read many tutorials and code samples, but they all seem to conveniently only look at ranges that don't wrap around the hue spectrum, or use solutions that are even uglier (eg. re-implementing cvInRangeS by iterating over every pixel and doing manual comparisons against a color table).
So, what's the usual way to solve this? What's the best way? What are the tradeoffs of each? Is iterating over pixels much slower than using built-in CV functions?
This is kind of late, but this is what I'd try.
Make the conversion: cvCvtColor(imageBgr, imageHsv, CV_RGB2HSV);
Note, RGB vs Bgr are purposefully being crossed.
This way, red color will be treated in a blue channel and will be centered around 170. There would also be a flip in direction, but that is OK as long as you know to expect it.
You can calculate Hue channel in range 0..255 with CV_BGR2HSV_FULL. Your original hue difference of 10 will become 14 (10/180*256), i.e. the hue must be in range 128-14..128+14:
public void inColorRange(CvMat imageBgr, CvMat dst, int color, int threshold) {
cvCvtColor(imageBgr, imageHsv, CV_BGR2HSV_FULL);
int rotation = 128 - color;
cvAddS(imageHsv, cvScalar(rotation, 0, 0), imageHsv);
cvInRangeS(imageHsv, cvScalar(128-threshold, 135, 135),
cvScalar(128+threshold, 255, 255), dst);
}
You won't believe but I had exactly the same issue and I solved it using simple iteration through Hue (not whole HSV) image.
Is iterating over pixels much slower than using built-in CV functions?
I've just tried to understood cv::inRange function but didn't get it at all (it seems that author used some specific iteration).
There is a really simple way of doing this.
First make two different color ranges
cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);
Then combine the two masks using addWeighted
cv::Mat red_hue_mask;
cv::addWeighted(lower_red_hue_range, 1.0, upper_red_hue_range, 1.0, 0.0, red_hue_mask);
Now you can just apply the mask to the image
cv::Mat result;
inputImageMat.copyTo(result, red_hue_mask);
I got the idea from a blog post I found
cvAddS(...) is equivalent, at element level, to:
out = static_cast<dest> ( in + shift );
This static_cast is the problem, because is clips/truncates the values.
A solution would be to shift the data from (0-180) to (x, 255), then apply a non-clipping add with overflow:
out = uchar(in + (255-180) + rotation );
Now you should be able to use a single InRange call, just shift your red interval according to the above formula