Using OpenCV to find the bounding box of numbers on an image - java

I'm trying to find the bounding box of the numbers in the middle of the below 3 images.
Here's 3 example cards I'm trying to work with.
The code I'm using is based (almost a complete copy) of the code provided in the first answer here, although converted to Java (answers in C++ are fine) and added parameters for the size of the contours to merge (defined as sizeHorizonal and sizeVertical in my code), which are the two parameters I'm playing with in the images below.
MatOfPoint2f approxCurve = new MatOfPoint2f();
Mat imgMAT = new Mat();
Utils.bitmapToMat(bmp32, imgMAT);
Mat grad = new Mat();
Imgproc.cvtColor(imgMAT, grad, Imgproc.COLOR_BGR2GRAY);
Mat img_sobel = new Mat();
Mat img_threshold = new Mat();
Imgproc.Sobel(grad, img_sobel, CvType.CV_8U, 1, 0, 3, 1, 0, Core.BORDER_DEFAULT);
Imgproc.threshold(img_sobel, img_threshold, 0, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(sizeHorizontal, sizeVertical));
Imgproc.morphologyEx(img_threshold, img_threshold, Imgproc.MORPH_CLOSE, element);
Imgproc.cvtColor(imgMAT, imgMAT, Imgproc.COLOR_BGR2GRAY);
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(img_threshold, contours, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE, new org.opencv.core.Point(0, 0));
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
org.opencv.core.Rect rect = Imgproc.boundingRect(points);
Imgproc.rectangle(imgMAT, rect.tl(), rect.br(), new Scalar(0, 255, 0), 2);
}
I've got the separate number sections contoured, but I can't find a way to isolate the contours I want. Here's the areas contoured with the parameters for the size input. As you can see for the second image, this is working exactly as I want and has contoured the whole number, rather than each section.
1: Size param input: 17, 5
2: Size param input: 23, 7
3: Size param input: 23, 13
So, things I need help with:
Isolating the four contours in the middle and finding a way to merge these contours together
I've thought about taking the contours that match a given aspect ratio and cropping to a bounding box encompassing all of them, but there are other surrounding contours with similar ratios.
Finding a way to choose the correct size parameters automatically (as each card type requires different parameters to isolate the numbers)
Short of trying all 3 size inputs and seeing what gives the expected contours, I could use the prevailing colour as an indicator of the card type and then use the parameters for this card type. But, any other suggestions would be helpful, as I feel there's a better way to do this.
Many thanks!

Out of my busy schedule i could help you to some extinct. Please find the code below which will help you for first two images. Fine tune it for the third image. Just play with morphological operations to get the required output.
//#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include "tchar.h"
using namespace cv;
using namespace std;
#define INPUT_FILE "p.jpg"
#define OUTPUT_FOLDER_PATH string("")
int _tmain(int argc, _TCHAR* argv[])
{
Mat large = imread(INPUT_FILE);
Mat rgb;
// downsample and use it for processing
pyrDown(large, rgb);
Mat small;
cvtColor(rgb, small, CV_BGR2GRAY);
// morphological gradient
Mat grad;
Mat morphKernel = getStructuringElement(MORPH_ELLIPSE, Size(2, 2));
Mat morphKernel1 = getStructuringElement(MORPH_ELLIPSE, Size(1, 1));
morphologyEx(small, grad, MORPH_GRADIENT, morphKernel);
// binarize
Mat bw;
threshold(grad, bw, 5.0, 50.0, THRESH_BINARY | THRESH_OTSU);
// connect horizontally oriented regions
Mat connected;
morphKernel = getStructuringElement(MORPH_RECT, Size(9, 1));
morphologyEx(bw, connected, MORPH_CLOSE, morphKernel);
morphologyEx(bw, connected, MORPH_OPEN, morphKernel1);
morphologyEx(connected, connected, MORPH_CLOSE, morphKernel);
morphologyEx(connected, connected, MORPH_CLOSE, morphKernel);
morphologyEx(connected, connected, MORPH_CLOSE, morphKernel);
// find contours
Mat mask = Mat::zeros(bw.size(), CV_8UC1);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(connected, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
// filter contours
int y=0;
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
Rect rect = boundingRect(contours[idx]);
Mat maskROI(mask, rect);
maskROI = Scalar(0, 0, 0);
// fill the contour
drawContours(mask, contours, idx, Scalar(255, 255, 255), CV_FILLED);
double a=contourArea( contours[idx],false);
if(a> 575)
{
rectangle(rgb, rect, Scalar(0, 255, 0), 2);
y++;
}
imshow("Result1",rgb);
}
cout<<" The number of elements"<<y<< endl;
imshow("Result",mask);
imwrite(OUTPUT_FOLDER_PATH + string("rgb.jpg"), rgb);
waitKey(0);
return 0;
}

Related

findContours in opencv Java

if (DETECT_RED_OBJECTS_ONLY)
{
Imgproc.cvtColor(inputFrame, gray,
Imgproc.COLOR_BGR2RGB);
}
else
{
Imgproc.cvtColor(inputFrame, gray, Imgproc.COLOR_BGR2GRAY);
}
Imgproc.cvtColor(inputFrame, dst, Imgproc.COLOR_BGR2RGB);
// down-scale and upscale the image to filter out the noise
Imgproc.pyrDown(gray, downscaled, new Size(gray.cols() / 2, gray.rows() 2));
Imgproc.pyrUp(downscaled, upscaled, gray.size());
if (DETECT_RED_OBJECTS_ONLY) {
// convert the image from RGBA to HSV
Imgproc.cvtColor(upscaled, hsv, Imgproc.COLOR_RGB2HSV);
// threshold the image for the lower and upper HSV red range
Core.inRange(hsv, HSV_LOW_RED1, HSV_LOW_RED2, lowerRedRange);
Core.inRange(hsv, HSV_HIGH_RED1, HSV_HIGH_RED2, upperRedRange);
// put the two thresholded images together
Core.addWeighted(lowerRedRange, 1.0, upperRedRange, 1.0, 0.0, bw);
// apply canny to get edges only
System.out.println(bw);
Imgproc.Canny(bw, bw, 0, 255);
} else {
// Use Canny instead of threshold to catch squares with gradient shading
Imgproc.Canny(upscaled, bw, 0, 255);
}
// dilate canny output to remove potential
// holes between edge segments
Imgproc.dilate(bw, bw, new Mat(), new Point(-1, 1), 1);
// find contours and store them all as a list
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
contourImage = bw.clone();
Imgproc.findContours(
contourImage,
contours,
hierarchyOutputVector,
Imgproc.RETR_EXTERNAL,
Imgproc.CHAIN_APPROX_SIMPLE
);
System.out.println("contours" + contours);
// loop over all found contours
for (MatOfPoint cnt : contours) {
MatOfPoint2f curve = new MatOfPoint2f(cnt.toArray());
// approximates a polygonal curve with the specified precision
Imgproc.approxPolyDP(
curve,
approxCurve,
0.02 * Imgproc.arcLength(curve, true),
true
);
int numberVertices = (int) approxCurve.total();
When I use triangle as an input image , this one works and detect triangle[counters become 1 and numberVertices becomes 3]. But when i input a rectangle image , contours become 2 and numberVertices becomes 2. It must be 4 . Isn't it. Can anyone help me to find the bug.
If you are using this in latest visual studio, I suggest you to use :
JavaList List<MatOfPoint> contours = new JavaList<MatOfPoint>();
this might change the behavior of your contours. Please let me know if this fixed your bug. Otherwise lets try with something else

OpenCV: Getting x co-ordinate from rect.br()

I am able to detect and draw a bounding rectangle around a region of interest like so:
Now I need to find the "x" value of the bottom horizontal line of the green bounding rectangle. My main aim is to find the "x" value of the base of the battery, and the "x" value of the blue strip of paper, so that I can calculate the distance between them.
There are only 2 values, rect.tl() and rect.br(), that I can see be used to draw the bounding rectangle Imgproc.rectangle(sourceMat, rect.tl(), rect.br(), green, 3);. I'm under the assumption that the "x" value from the bottom right point of the bounding rectangle will be the x co-ordinate of the pixel point.
How do i get the 'x' value from rect.br()
When i print out rect.br() like so Log.e("BR", rect.br().toString()); i get this:
E/BR: {793.0, 1686.0}
private Bitmap findRoi(Bitmap sourceBitmap) {
Bitmap roiBitmap = null;
Scalar green = new Scalar(0, 255, 0, 255);
Mat sourceMat = new Mat(sourceBitmap.getWidth(), sourceBitmap.getHeight(), CvType.CV_8UC3);
Utils.bitmapToMat(sourceBitmap, sourceMat);
Mat roiTmp = sourceMat.clone();
final Mat hsvMat = new Mat();
sourceMat.copyTo(hsvMat);
// convert mat to HSV format for Core.inRange()
Imgproc.cvtColor(hsvMat, hsvMat, Imgproc.COLOR_RGB2HSV);
Scalar lowerb = new Scalar(85, 50, 40); // lower color border for BLUE
Scalar upperb = new Scalar(135, 255, 255); // upper color border for BLUE
Core.inRange(hsvMat, lowerb, upperb, roiTmp); // select only blue pixels
// find contours
List<MatOfPoint> contours = new ArrayList<>();
List<RotatedRect> boundingRects = new ArrayList<>();
Imgproc.findContours(roiTmp, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
// find appropriate bounding rectangles
for (MatOfPoint contour : contours) {
MatOfPoint2f areaPoints = new MatOfPoint2f(contour.toArray());
RotatedRect boundingRect = Imgproc.minAreaRect(areaPoints);
double rectangleArea = boundingRect.size.area();
// test min ROI area in pixels
if (rectangleArea > 40001) {//400000
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
if (rect.width > rect.height) {
Imgproc.rectangle(sourceMat, rect.tl(), rect.br(), green, 3);
}
}
}
roiBitmap = Bitmap.createBitmap(sourceMat.cols(), sourceMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(sourceMat, roiBitmap);
return roiBitmap;
}
I don't know a lot about Java but AFAIK it must be similar to c++, so whether you know about it or not,I try my code in c++. Suppose that you are working on the below rectangle:
For having the above Rect in OpenCV:
Rect r = Rect(20,20,30,20); // Rect(x,y,width,height)
And we could access the below-right x with using the br() like the below.
int x2=r.br().x ;
Below pic will tell you the whole story.

OpenCV Android (java) character detection and font recognition

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);
}
}

Light reflexion mitigation with findCountours() using OpenCV?

I'm quite new to OpenCV but after some hours working on it, I'm beggining to understand its workings. I'm working on a leaf recognition functionality using OpenCV-2.4.11 in Java but there are some problems because of light reflexion.
Here is the original image:
I've applied some transformations on it to get the contours but the light reflexion is causing problems.
I used the commands below:
Imgproc.cvtColor(dst, dst, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(dst, dst, 0, 255, Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU);
and it produced the following result:
Now, since I'm interested on the contours, I applied
ArrayList<MatOfPoint> points = new ArrayList<>();
Imgproc.findContours(dst, points, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
Imgproc.drawContours(dst, points, -1, new Scalar(255, 0, 0), 1);
and it gave me
The defects are caused by the light reflexion and I don't know how to deal with it.
I tried using HLS colorspace reducing the L channel values (of the original image) to 60% of its original values, so the image became like this:
Over that, I ran the following commands`
Imgproc.cvtColor(dst, dst, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(dst, dst, 0, 255, Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU);
ArrayList<MatOfPoint> points = new ArrayList<>();
Imgproc.findContours(dst, points, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
Imgproc.drawContours(dst, points, -1, new Scalar(255, 0, 0), 1);
Resulting in
Since I don't have much knowledge of OpenCV or Image processing, I'm stuck on this issue.
Does anyone know how to solve (or mitigate) this problem?
Thanks in advance!
my trial code based on HoughLinesP is below. i hope it helps.
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src, src_gray, edges;
char* filename = argc >= 2 ? argv[1] : (char*)"ibqYp.jpg";
src = imread( filename, 1 );
if( src.empty() )
{
return -1;
}
cvtColor( src, src_gray, COLOR_RGB2GRAY );
Canny( src_gray, edges, 50, 200, 3 );
vector<Vec4i> p_lines;
HoughLinesP( edges, p_lines, 1, CV_PI/180, 50, 30, 10 );
for( size_t i = 0; i < p_lines.size(); i++ )
{
Vec4i l = p_lines[i];
if( abs(l[2]-l[0]) > abs(l[3]-l[1])* 3 ) // this is for filter vertical lines
line( src, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,255,0), 1, LINE_AA);
}
cvtColor( src, src_gray, COLOR_RGB2GRAY );
src_gray = src_gray < 190;
imshow("result", src_gray );
waitKey(0);
return 0;
}
Result image ( you can find the external contours easily )

Trouble detecting skin

I'm trying to detect skin on images using opencv (new at it) but I've managed to get SOME of the skin to be detected, however the rest seems to cause some noise on the image. Here's the original image:
The result of my code is:
The code that prouduces this:
Mat image = Imgcodecs.imread(name);
Imgproc.pyrMeanShiftFiltering(image, image, 10, 20);
Imgproc.blur(image, image, new Size(3, 3));
Mat hsv = new Mat();
Imgproc.cvtColor(image, hsv, Imgproc.COLOR_BGR2HSV);
Mat bw = new Mat();
Core.inRange(hsv, new Scalar(0, 10, 60), new Scalar(20, 150, 255), bw);
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(bw, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0));
int s = findBiggestContour(contours);
Mat drawing = new Mat(image.size(), CvType.CV_8UC1);
Imgproc.drawContours(drawing, contours, s, new Scalar(255), -1, 8, hierarchy, 0, new Point(0, 0));
Imgcodecs.imwrite(rename(name), drawing);
How do I fix the code to detect the remaining skin on the image and get rid of the noise?
I'm using Java with OpenCV 3.0.0.
Since you're using the findBiggestConour() I think you only draw the biggest match, not all of them. Since the biggest contour happens to be the one on the second image only that is shown.
Just to add to what JanSLO said here above, I tried your code and instead of drawing just the biggest contour, I drew all contours and got the following result.
//c++ code, not java
Mat drawing = Mat::zeros(img.size(), CV_8UC1 );
for(int i=0; i < contours.size(); ++i) {
drawContours(drawing, contours, i, Scalar(255), 3, 8, hierarchy, 0, Point(0, 0));
}
imwrite("data/skin_detect_out.jpg", drawing);
I am pleasantly surprised at the result, since this is such a simple piece of code. More advanced pixel based skin detection methods involve making a probability model of skin pixels using training data, and using that model to classify whether a given pixel is skin or not.

Categories