I'm new to OpenCV, and I want to work on object detection to help my FRC robotics team. I'm trying to use an HSV filter and HoughCircles to find a tennis ball in a webcam image and draw a circle around it. Here is my code:
Mat currentFrame = new Mat();
Mat hsv = new Mat();
Mat threshImage = new Mat();
Mat circles = new Mat();
while (true) {
camera.read(currentFrame);
Imgproc.resize(currentFrame, currentFrame, new Size(WIDTH, HEIGHT));
Imgproc.cvtColor(currentFrame, hsv, Imgproc.COLOR_RGB2HSV);
hsvWindow.showImage(hsv);
Core.inRange(hsv, new Scalar(50, 100, 0), new Scalar(95, 255, 255), threshImage);
threshWindow.showImage(threshImage);
Imgproc.HoughCircles(threshImage, circles, Imgproc.CV_HOUGH_GRADIENT, 2, 100, 100, 100, 0, 500);
for (int i = 0; i < circles.cols(); i++) {
double[] vCircle = circles.get(0, i);
Point pt = new Point(Math.round(vCircle[0]), Math.round(vCircle[1]));
int radius = (int)Math.round(vCircle[2]);
Core.circle(currentFrame, pt, radius, new Scalar(255, 0, 0), 2);
}
drawWindow.showImage(currentFrame);
}
The original image, the hsv image, and the filtered image are in this album: http://imgur.com/a/hO8vs
When I run HoughCircles with the parameters here, it finds circles on the piano bench and toy rabbit, but not the tennis ball, which appears as a big white circle.
I fixed it! After some fiddling with the parameters of HoughCircles and blurring and thresholding the binary image it was finding it reliably, but the circle was jittery and inconsistent. So, I replaced HoughCircles with findContours, cycled through the contours looking for the largest one, and used minEnclosingCircle. Here is the code now:
Mat currentFrame = new Mat(), hsv = new Mat(), threshImage = new Mat();
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
while (true) {
camera.read(currentFrame);
Imgproc.resize(currentFrame, currentFrame, new Size(WIDTH, HEIGHT));
Imgproc.cvtColor(currentFrame, hsv, Imgproc.COLOR_RGB2HSV);
hsvWindow.showImage(hsv);
Core.inRange(hsv, new Scalar(50, 100, 50), new Scalar(95, 255, 255), threshImage);
Imgproc.blur(threshImage, threshImage, new Size(10, 10));
Imgproc.threshold(threshImage, threshImage, 150, 255, Imgproc.THRESH_BINARY);
threshWindow.showImage(threshImage);
Imgproc.findContours(threshImage, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
double maxArea = 0;
float[] radius = new float[1];
Point center = new Point();
for (int i = 0; i < contours.size(); i++) {
MatOfPoint c = contours.get(i);
if (Imgproc.contourArea(c) > maxArea) {
MatOfPoint2f c2f = new MatOfPoint2f(c.toArray());
Imgproc.minEnclosingCircle(c2f, center, radius);
}
}
Core.circle(currentFrame, center, (int)radius[0], new Scalar(255, 0, 0), 2);
drawWindow.showImage(currentFrame);
}
I know this might not be particularly helpful for people looking to use HoughCircles specifically, but it's a testament to the power of blurring binary images. If you're looking for a circle among many things, you look for contours and compare the contour area to the area of its enclosing circle.
Related
I have to take shelf plans. I would like to order them as if it were a matrix where [0,0] was at the bottom left or top left.
I need this because there will be a database with coordinates to take something on the shelf.
But findContours takes the contours randomly and I don't know how to sort them.
How can I do?
I hope I was clear.
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
Mat img = inputFrame.rgba();
Imgproc.cvtColor(img, gray, Imgproc.COLOR_BGR2GRAY, 0);
Imgproc.Canny(gray, gray, 100, 255, 3, true);
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, kernelSize);
Imgproc.dilate(gray, gray, kernel);
Imgproc.GaussianBlur(gray, gray, new Size(3, 3), 0);
List < MatOfPoint > contours = new ArrayList < MatOfPoint > ();
Imgproc.findContours(gray, contours, hierarchy, Imgproc.RETR_TREE,
Imgproc.CHAIN_APPROX_SIMPLE);
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());
double approxDistance = Imgproc.arcLength(contour2f, true) * 0.05;
Imgproc.approxPolyDP(contour2f, approxCurve, approxDistance, true);
//convert back to MatOfPoint
MatOfPoint points = new MatOfPoint(approxCurve.toArray());
//SORT POINTs
//Get bounding rect of contour
Rect rect = Imgproc.boundingRect(points);
//draw enclosing rectangle
if (rect.height > 200 && rect.width > 200)
// Imgproc.rectangle(img,new Point(rect.x,rect.y),new
Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 0, 255), 4);
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(0, 0, 255), 4);
}
return img;
}
I'm trying to get this rectangle from that image:
Found this solution using OpenCV:
private Bitmap findRectangle(Bitmap src) throws Exception {
Mat imageMat = new Mat();
Utils.bitmapToMat(src, imageMat);
Mat imgSource=imageMat.clone();
Imgproc.cvtColor(imgSource, imageMat, Imgproc.COLOR_BGR2GRAY);
//find the contours
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(imageMat, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE);
Imgproc.Canny(imageMat,imageMat,0,255);
Bitmap canny=Bitmap.createBitmap(imageMat.cols(),imageMat.rows(),Bitmap.Config.ARGB_8888);
Utils.matToBitmap(imageMat,canny);
Imgproc.GaussianBlur(imageMat, imageMat, new org.opencv.core.Size(1, 1), 2, 2);
Bitmap blur=Bitmap.createBitmap(imageMat.cols(),imageMat.rows(),Bitmap.Config.ARGB_8888);
Utils.matToBitmap(imageMat,blur);
MatOfPoint temp_contour = contours.get(0); //the largest is at the index 0 for starting point
for (int idx = 0; idx < contours.size(); idx++) {
temp_contour = contours.get(idx);
//check if this contour is a square
MatOfPoint2f new_mat = new MatOfPoint2f( temp_contour.toArray() );
int contourSize = (int)temp_contour.total();
MatOfPoint2f approxCurve_temp = new MatOfPoint2f();
Imgproc.approxPolyDP(new_mat, approxCurve_temp, contourSize*0.05, true);
if (approxCurve_temp.total() == 4) {
MatOfPoint points = new MatOfPoint( approxCurve_temp.toArray() );
Rect rect = Imgproc.boundingRect(points);
Imgproc.rectangle(imgSource, new Point(rect.x,rect.y), new Point(rect.x+rect.width,rect.y+rect.height), new Scalar(255, 0, 0, 255), 3);
}
}
Bitmap analyzed=Bitmap.createBitmap(imgSource.cols(),imgSource.rows(),Bitmap.Config.ARGB_8888);
Utils.matToBitmap(imgSource,analyzed);
return analyzed;
}
The best i got was this:
The problem is that the rectangle isn't perfect, maybe find the white numbers inside of that can be a best option, but i don't know too much of OpenCV.
Original image:
This is a very simple C++ implementation which tries to search for the text box. The accuracy of the detection depends on three parameters:
The thresh value provided to cv::threshold function to convert gray image to binary.
The height/width ratio, since the height of the text box is relatively smaller than the width, and the area of the text box.
Mat img = imread("image.jpg",-1), gray, binary;
/*pre-processing steps*/
uchar thresh = 80;
cvtColor(img, gray, cv::COLOR_BGR2GRAY);
GaussianBlur(gray, gray, Size(7,7), 0);
// change the thresh value to fine tune this program for your images
threshold(gray, binary, thresh, 255, cv::THRESH_BINARY_INV);
/*contour searching*/
std::vector<std::vector<Point>> contours;
std::vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
/*Filtering contours based on height/width ratio and bounding box area*/
std::vector<Rect> boxes;
double box_ratio = 0.3;
int box_area = 20000;
for(auto& cnt : contours)
{
auto box = minAreaRect(cnt).boundingRect();
// we are searching for a rectangle which a has relatively large area,
// and the height is smaller than the width, so the
// height/width ratio should be small. Change the these two values for fine tuning
if((min(box.width,box.height)/double(max(box.width,box.height)) < box_ratio) && box.area() > box_area )
{
boxes.push_back(box);
}
}
Mat txt_box = img(boxes.at(0));
Here is the almost same solution on java:
private Bitmap findRoi(Bitmap sourceBitmap) {
Mat sourceMat = new Mat(sourceBitmap.getWidth(), sourceBitmap.getHeight(), CV_8UC3);
Utils.bitmapToMat(sourceBitmap, sourceMat);
Mat grayMat = new Mat(sourceBitmap.getWidth(), sourceBitmap.getHeight(), CV_8UC3);
Imgproc.cvtColor(sourceMat, grayMat, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(grayMat, grayMat, 125, 200, Imgproc.THRESH_BINARY);
// find contours
List<MatOfPoint> whiteContours = new ArrayList<>();
Rect largestRect = null;
Imgproc.findContours(grayMat, whiteContours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
// find appropriate bounding rectangles
for (MatOfPoint contour : whiteContours) {
RotatedRect boundingRect = Imgproc.minAreaRect(new MatOfPoint2f(contour.toArray()));
double rectangleArea = boundingRect.size.area();
// test min ROI area in pixels
if (rectangleArea > 10000) {
Point rotated_rect_points[] = new Point[4];
boundingRect.points(rotated_rect_points);
Rect rect = Imgproc.boundingRect(new MatOfPoint(rotated_rect_points));
// test horizontal ROI orientation and aspect ratio
if (rect.width > 3 * rect.height) {
if (largestRect == null) {
largestRect = rect;
} else {
if (rect.width > largestRect.width) {
largestRect = rect;
}
}
}
}
}
Mat roiMat = new Mat(sourceMat, largestRect);
Bitmap bitmap = Bitmap.createBitmap(roiMat.cols(), roiMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(roiMat, bitmap);
return bitmap;
}
Also, you can use additional information: red number places on the right.
I am working on a program that extracts the stickers on the puzzle and then later on finds the RGB of them. Currently, I am at the point where I want to remove any contours that aren't "square" like. I was wondering how I could do this.
What I do is I load the image, gray it, blur it, canny edge detection, dilate it find contours and draw them.
Is there a way I can draw around the contours instead of filling them in? And remove contours that aren't roughly the same size around or have almost 90 degree angles?
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat capturedFrame = Imgcodecs.imread("first.png");
//Gray
Mat gray = new Mat();
Imgproc.cvtColor(capturedFrame, gray, Imgproc.COLOR_BGR2GRAY);
//Blur
Mat blur = new Mat();
Imgproc.blur(gray, blur, new Size(3,3));
//Canny image
Mat canny = new Mat();
Imgproc.Canny(blur, canny, 20, 40, 3, true);
Imgcodecs.imwrite("test.png", canny);
//System.exit(0);
Mat kernel = Imgproc.getStructuringElement(1, new Size(3,3));
Mat dilated = new Mat();
Imgproc.dilate(canny,dilated, kernel);
List<MatOfPoint> contours = new ArrayList<>();
//find contours
Imgproc.findContours(dilated, contours, new Mat(), Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);
//draw contours
Imgproc.cvtColor(capturedFrame, capturedFrame, Imgproc.COLOR_BGR2RGB);
for(int i = 0; i < contours.size(); i++){
Imgproc.drawContours(capturedFrame, contours, i, new Scalar(0, 0, 255), -1);
}
Imgcodecs.imwrite("after.png", capturedFrame);
Imshow img = new Imshow("firstImg");
img.show(capturedFrame);
}
Here is the initial image:
Here is the image with the contours drawn:
To draw non filled contours use non negative thickness: Imgproc.drawContours(capturedFrame, contours, i, new Scalar(0, 0, 255), 1); for instance.
To remove unnecessary find contour area, and just skip too large or too small of them when drawing.
I wrote this code that detects rectangle but I can not write a code that detects corners.
public class RectDetection {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat rectengle=Imgcodecs.imread("D:\\sepano\\rect.png");
Mat img =new Mat();
img=rectengle.clone();
Imgproc.cvtColor(rectengle, img, Imgproc.COLOR_BGR2GRAY);
Imgproc.GaussianBlur(img, img, new org.opencv.core.Size(1, 1), 2, 2);
Imgproc.Canny(img,img,3, 3,5,false);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(img, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
MatOfPoint temp_contour = contours.get(0); //the largest is at the index 0 for starting point
for (int idx = 0; idx < contours.size(); idx++) {
temp_contour = contours.get(idx);
MatOfPoint2f new_mat = new MatOfPoint2f( temp_contour.toArray() );
int contourSize = (int)temp_contour.total();
MatOfPoint2f approxCurve_temp = new MatOfPoint2f();
Imgproc.approxPolyDP(new_mat, approxCurve_temp, contourSize*0.05, true);
if (approxCurve_temp.total()==8) {
MatOfPoint points = new MatOfPoint( approxCurve_temp.toArray() );
Rect rect = Imgproc.boundingRect(points);
Imgproc.rectangle(img, new Point(rect.x,rect.y), new Point(rect.x+rect.width,rect.y+rect.height), new Scalar(170,0,150,0), 5);}}
Here is a python code for corner detection but I can not convert it to java:
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('simple.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray,25,0.01,10)
corners = np.int0(corners)
for i in corners:
x,y = i.ravel()
cv2.circle(img,(x,y),3,255,-1)
plt.imshow(img),plt.show()
can any help me????
Look at your java code closely...
In this line:
Imgproc.rectangle(img, new Point(rect.x,rect.y), new Point(rect.x+rect.width,rect.y+rect.height), new Scalar(170,0,150,0), 5);
Point(rect.x,rect.y) corresponds to the top left corner of your rectangle, and Point(rect.x+rect.width,rect.y+rect.height) corresponds to the bottom right corner of your rectangle.
Rectangle detection code should suffice, and the 4 corners are as follows:
Point(rect.x,rect.y) //Top Left
Point(rect.x+rect.width,rect.y) //Top Right
Point(rect.x,rect.y+rect.height) //Bottom Left
Point(rect.x+rect.width,rect.y+rect.height) //Bottom Right
I am trying to implement some image processing using OpenCV and Java to extract a card out of an image.
Following is my approach:
Convert to BGR image
Convert to GRAY image
Apply GaussianBlur
Apply Canny Edge detection
Dilate
Find contours
Find the largest contour
Find corners of the largest contour using approxPolyDP
Getting a top-down view of the cropped image along the largest contour
At step no 8, I am facing some issues, as I am not getting the appropriate corners/vertices. Following sample images shows the scenario :
The original Image
After edge detection and dilation. (What is to be done to get appropriate edges?? Here I've got broken edges. Could not get Hough transform working)
After finding vertices. (shown in green)
Following is the code :
System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
//load Image
File input = new File("card4.png");
BufferedImage image = ImageIO.read(input);
byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
//put read image to Mat
mat = new Mat(image.getHeight(), image.getWidth(), CvType.CV_8UC3); //original Mat
mat.put(0, 0, data);
mat_f = new Mat(image.getHeight(), image.getWidth(), CvType.CV_8UC3); //for storing manipulated Mat
//conversion to grayscale, blurring and edge detection
Imgproc.cvtColor(mat, mat_f, Imgproc.COLOR_RGB2BGR);
Imgproc.cvtColor(mat_f, mat_f, Imgproc.COLOR_RGB2GRAY);
Imgproc.GaussianBlur(mat_f, mat_f, new Size(13,13), 0);
Imgproc.Canny(mat_f, mat_f, 300, 600, 5, true);
Imgproc.dilate(mat_f, mat_f, new Mat(), new Point(-1, -1), 2);
Imgcodecs.imwrite("D:\\JAVA\\Image_Proc\\CVTest1.jpg",mat_f);
//finding contours
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(mat_f, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
double maxArea=0;
int maxAreaIdx=0;
//finding largest contour
for (int idx = 0; idx != contours.size(); ++idx)
{
Mat contour = contours.get(idx);
double contourarea = Imgproc.contourArea(contour);
if (contourarea > maxArea)
{
maxArea = contourarea;
maxAreaIdx = idx;
}
}
//Rect rect = Imgproc.boundingRect(contours.get(maxAreaIdx));
//Imgproc.rectangle(mat, new Point(rect.x,rect.y), new Point(rect.x+rect.width,rect.y+rect.height),new Scalar(0,0,255),7);
// mat = mat.submat(rect.y, rect.y + rect.height, rect.x, rect.x + rect.width);
//Polygon approximation
MatOfPoint2f approxCurve = new MatOfPoint2f();
MatOfPoint2f oriCurve = new MatOfPoint2f(contours.get(maxAreaIdx).toArray());
Imgproc.approxPolyDP(oriCurve, approxCurve, 6.0, true);
//drawing red markers at vertices
Point [] array = approxCurve.toArray();
for(int i=0; i < array.length;i++) {
Imgproc.circle(mat, array[i], 2, new Scalar(0, 255, 0), 5);
}
Imgcodecs.imwrite("D:\\JAVA\\Image_Proc\\CVTest.jpg",mat);
Seeking help in getting the appropriate corner vertices...
Thanks in advance..
In order to archive the good result using your approach then your cards have to contain 4 corners. But i prefer to use the HoughLine approach for this task.
Step 1: Resize image for higher performance
Step 2: Edges detection
Transform the image into gray scale
Blur image to clear noises
Edge detection using Canny filters
You can use the dilation for make the white bigger for the next step
Step 3: Find card's corners
Find contour of image
From the list of contour get the largest contour
Get the convexHull of it
Use approxPolyDP to simplify the convex hull (this should give a quadrilateral)
From now you can draw contour to get the rectangle after restore scale
From the quadrilateral you can get the 4 corners.
Find Homography
Warp the input image using the computed homography matrix
Here is sample code in Java
// STEP 1: Resize input image to img_proc to reduce computation
double ratio = DOWNSCALE_IMAGE_SIZE / Math.max(frame.width(), frame.height());
Size downscaledSize = new Size(frame.width() * ratio, frame.height() * ratio);
Mat dst = new Mat(downscaledSize, frame.type());
Imgproc.resize(frame, dst, downscaledSize);
Mat grayImage = new Mat();
Mat detectedEdges = new Mat();
// STEP 2: convert to grayscale
Imgproc.cvtColor(dst, grayImage, Imgproc.COLOR_BGR2GRAY);
// STEP 3: try to filter text inside document
Imgproc.medianBlur(grayImage, detectedEdges, 9);
// STEP 4: Edge detection
Mat edges = new Mat();
// Imgproc.erode(edges, edges, new Mat());
// Imgproc.dilate(edges, edges, new Mat(), new Point(-1, -1), 1); // 1
// canny detector, with ratio of lower:upper threshold of 3:1
Imgproc.Canny(detectedEdges, edges, this.threshold.getValue(), this.threshold.getValue() * 3, 3, true);
// STEP 5: makes the object in white bigger to join nearby lines
Imgproc.dilate(edges, edges, new Mat(), new Point(-1, -1), 1); // 1
Image imageToShow = Utils.mat2Image(edges);
updateImageView(cannyFrame, imageToShow);
// STEP 6: Compute the contours
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(edges, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
// STEP 7: Sort the contours by length and only keep the largest one
MatOfPoint largestContour = getMaxContour(contours);
// STEP 8: Generate the convex hull of this contour
Mat convexHullMask = Mat.zeros(frame.rows(), frame.cols(), frame.type());
MatOfInt hullInt = new MatOfInt();
Imgproc.convexHull(largestContour, hullInt);
MatOfPoint hullPoint = OpenCVUtil.getNewContourFromIndices(largestContour, hullInt);
// STEP 9: Use approxPolyDP to simplify the convex hull (this should give a quadrilateral)
MatOfPoint2f polygon = new MatOfPoint2f();
Imgproc.approxPolyDP(OpenCVUtil.convert(hullPoint), polygon, 20, true);
List<MatOfPoint> tmp = new ArrayList<>();
tmp.add(OpenCVUtil.convert(polygon));
restoreScaleMatOfPoint(tmp, ratio);
Imgproc.drawContours(convexHullMask, tmp, 0, new Scalar(25, 25, 255), 2);
// Image extractImageToShow = Utils.mat2Image(convexHullMask);
// updateImageView(extractFrame, extractImageToShow);
MatOfPoint2f finalCorners = new MatOfPoint2f();
Point[] tmpPoints = polygon.toArray();
for (Point point : tmpPoints) {
point.x = point.x / ratio;
point.y = point.y / ratio;
}
finalCorners.fromArray(tmpPoints);
boolean clockwise = true;
double currentThreshold = this.threshold.getValue();
if (finalCorners.toArray().length == 4) {
Size size = getRectangleSize(finalCorners);
Mat result = Mat.zeros(size, frame.type());
// STEP 10: Homography: Use findHomography to find the affine transformation of your paper sheet
Mat homography = new Mat();
MatOfPoint2f dstPoints = new MatOfPoint2f();
Point[] arrDstPoints = { new Point(result.cols(), result.rows()), new Point(0, result.rows()), new Point(0, 0), new Point(result.cols(), 0) };
dstPoints.fromArray(arrDstPoints);
homography = Calib3d.findHomography(finalCorners, dstPoints);
// STEP 11: Warp the input image using the computed homography matrix
Imgproc.warpPerspective(frame, result, homography, size);
}