I am working on a licence plate recognition software using OpenCV, Tesseract and Java but experiencing issues, I cant seem to segment my text correctly, its not always that I get all characters to be detected and bounded with a bounding box these are some of my outputs with my code...and also when I detect the characters I never know which character is in which box so when I pass them through tesseract they get jumbled, how do I format my string?
This one fails despite the clearly visible characters:
Here Z and 6 fail to be detected even when 6 is clearly visible:
Below is my code:
originalFrame = image.clone();
roiColor = image.clone();
Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2GRAY, 0);
originalFrameGrayScale = image.clone();
Mat morph = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(9, 9));
Imgproc.morphologyEx(image, image, Imgproc.MORPH_TOPHAT, morph);
Imgproc.Sobel(image, image, -1, 2, 0);
Imgproc.GaussianBlur(image, image, new Size(5,5), 3,3);
Imgproc.morphologyEx(image, image, Imgproc.MORPH_CLOSE, morph);
Imgproc.threshold(image, image, 200, 255, Imgproc.THRESH_OTSU);
Vector<Rect> rectangles = detectionContour(image);
Mat roi = originalFrameGrayScale.clone();
if(!rectangles.isEmpty()){
roi = originalFrameGrayScale.submat(rectangles.get(0));
roiBlack = roi.clone();
roiColor = roiColor.submat(rectangles.get(0));
Imgproc.rectangle(originalFrame, rectangles.get(0).br(), rectangles.get(0).tl(), new Scalar(0,0,255), 2);
}
Imgproc.medianBlur(roi, roi, 3);
Imgproc.adaptiveThreshold(roi, roi, 225, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 15, 3);
roiBinarize = roi.clone();
Mat erode = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, 1));
Mat dilate = Imgproc.getStructuringElement(Imgproc.MORPH_RECT,new Size(1, 1));
Imgproc.morphologyEx(roi, roi, Imgproc.MORPH_OPEN, dilate);
Imgproc.morphologyEx(roi, roi, Imgproc.MORPH_OPEN, erode);
Imgproc.Canny(roi, roi, 150, 150 * 3, 3, true);
Vector<Rect> letters = detectionPlateCharacterContour(roi);
doTesseractOCR(letters, roiBinarize);
private static void doTesseractOCR(Vector<Rect> letters, Mat plate){
Tesseract instance = new Tesseract(); //
instance.setLanguage(LANGUAGE);
String resultPlate = "";
for(int i= 0; i < letters.size(); i++){
BufferedImage letter = OpenCvUtils.Mat2bufferedImage(plate.submat(letters.get(i)));
try {
String result = instance.doOCR(letter);
resultPlate += result + " position "+i;
} catch (TesseractException e) {
System.err.println(e.getMessage());
}
System.out.println("Tesseract output: "+resultPlate);
}
}
private static Vector<Rect> detectionPlateCharacterContour(Mat roi) {
Mat contHierarchy = new Mat();
Mat imageMat = roi.clone();
Rect rect = null;
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(imageMat, contours, contHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
Vector<Rect> rect_array = new Vector<>();
for (int i = 0; i < contours.size(); i++) {
rect = Imgproc.boundingRect(contours.get(i));
double ratio = 0;
if(rect.height > rect.width){
ratio = rect.height/rect.width;
}else{
ratio = rect.width/rect.height;
}
Logger.printMessage("Ratio of letter: "+ratio);
double contourarea = Imgproc.contourArea(contours.get(i));
if (contourarea >= 100 && contourarea <= 1000 && ( ratio >= 1 && ratio <= 2)) {
Imgproc.rectangle(roiColor, rect.br(), rect.tl(), new Scalar(255,0,0));
rect_array.add(rect);
}
}
contHierarchy.release();
return rect_array;
}
Related
I am performing Canny edge detector using Android and Opencv on an image to detect the largest contour, extract it using warpPerspective method then find all objects inside that contour. Everything is working as expected but only for image that isn't rotated.
I am using boundingRect to get the contour and use its coordinates to extract it.
Here my code:
private Mat detectLargestContour(Mat origMat) {
// long e1 = Core.getTickCount();
Mat mGray = new Mat();
MatOfDouble mu = new MatOfDouble();
MatOfDouble stddev = new MatOfDouble();
Imgproc.cvtColor(origMat, mGray, Imgproc.COLOR_BGR2GRAY);
Core.meanStdDev(mGray, mu, stddev);
Imgproc.GaussianBlur(mGray, mGray, new Size(5, 5), 5);
//Imgproc.Canny(mGray, mGray, 30, 80, 3, false); //FOR HIGH BRIGHTNESS
//Imgproc.Canny(mGray, mGray, 50, 130, 3, false); // FOR LOW BRIGHTNESS
Imgproc.Canny(mGray, mGray, mu.get(0, 0)[0], stddev.get(0, 0)[0], 3, false);
Mat kernell = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(9,9));
Imgproc.morphologyEx(mGray, mGray, Imgproc.MORPH_CLOSE, kernell);
Imgproc.dilate(mGray, mGray, Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(3, 3)));
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(mGray, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//MatOfPoint2f approxCurve = new MatOfPoint2f();
double largest_area=0;
Rect rect = new Rect();
for (int idx = 0; idx < contours.size() ; idx++) {
double a = Imgproc.contourArea(contours.get(idx)); //Find the area of contour
if (a > largest_area) {
largest_area = a;
rect = Imgproc.boundingRect(contours.get(idx));
}
}
if (rect.area() > 100000) {
Imgproc.rectangle(origMat, rect.tl(), rect.br(), new Scalar(0, 255, 0));
p1 = new Point(rect.tl().x, rect.tl().y);
p2 = new Point(rect.tl().x + rect.width, rect.tl().y);
p3 = new Point(rect.tl().x, rect.tl().y + rect.height);
p4 = new Point(rect.tl().x + rect.width, rect.tl().y + rect.height);
card_corners = new ArrayList<>();
card_corners.add(p1);
card_corners.add(p3);
card_corners.add(p4);
card_corners.add(p2);
warpedCard = new Mat(origMat.rows(), origMat.cols(), CvType.CV_8UC3);
final Point p1 = new Point(warpedCard.cols() + marge, warpedCard.rows() + marge);
final Point p2 = new Point(0 - marge, warpedCard.rows() + marge);
final Point p3 = new Point(0 - marge, 0 - marge);
final Point p4 = new Point(warpedCard.cols() + marge, 0 - marge);
LinkedList<Point> sceneList = new LinkedList<Point>();
sceneList.addLast(p4);
sceneList.addLast(p3);
sceneList.addLast(p2);
sceneList.addLast(p1);
MatOfPoint2f scene = new MatOfPoint2f();
scene.fromList(sceneList);
MatOfPoint2f obj = new MatOfPoint2f();
obj.fromList(card_corners);
Mat homography = Calib3d.findHomography(obj, scene);
Imgproc.warpPerspective(origMat, warpedCard, homography, new Size(warpedCard.cols(), warpedCard.rows()));
return warpedCard;
}
return origMat;
}
It's weird but only boundingRect gave me a stable and performant result but the drawn rectangle doesn't rotate with the found contour.
How can I manage to resolve this issue? Any thoughts?
EDIT:
I changed boundingRect with minAreaRect.
Here is the code
int largest_idx = 0;
for (int idx = 0; idx < contours.size() ; idx++) {
double a = Imgproc.contourArea(contours.get(idx)); //Find the area of contour
if (a > largest_area) {
largest_area = a;
// rect = Imgproc.boundingRect(contours.get(idx));
largest_idx = idx;
}
}
MatOfPoint2f new_mat = new MatOfPoint2f( contours.get(largest_idx).toArray() );
RotatedRect rbox = Imgproc.minAreaRect(new_mat);
Log.d("rotatedrect_angle", "" + rbox.angle);
Point points[] = new Point[4];
rbox.points(points);
for(int i=0; i<4; ++i){
Imgproc.line(origMat, points[i], points[(i+1)%4], new Scalar(255,255,255));
}
And here is what I've got:
As you can see the detection isn't as accurate as when I used boundingRect.
A Python demo to find and draw a rotatedRect:
# 2019/03/01
# https://stackoverflow.com/a/54942835/3547485
import numpy as np
import cv2
gray = cv2.imread("tmp.png", cv2.IMREAD_GRAYSCALE)
th, threshed = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV)
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]
cnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
rbox = cv2.minAreaRect(cnt)
pts = cv2.boxPoints(rbox).astype(np.int32)
cv2.drawContours(img, [pts], -1, (0, 255, 0), 1, cv2.LINE_AA)
cv2.imwrite("dst.png", img)
Useful OpenCV functions(in Python) : cv2.minAreaRect, cv2.boxPoints, cv.2drawContours. You can find corresponding functions in Java.
I'd like to copy the recognized text to a clean image using createBitmap but I dont know how to get it from the boxes on the original image.
This code finds Maximally stable extremal regions and highlights them on the bitmap. Further below is what I get wit it on a sample image.
private void sgmnt(Mat mImg) {
Imgproc.cvtColor(msImg, m2, Imgproc.COLOR_RGB2GRAY);
Mat mRgba = mImg;
Mat mGray = m2;
Scalar CONTOUR_COLOR = new Scalar(1, 1, 255, 1);
//Scalar CONTOUR_COLOR = new Scalar(255);
MatOfKeyPoint keyPoint = new MatOfKeyPoint();
List<KeyPoint> listPoint = new ArrayList<>();
KeyPoint kPoint = new KeyPoint();
Mat mask = Mat.zeros(mGray.size(), CvType.CV_8UC1);
int rectanx1;
int rectany1;
int rectanx2;
int rectany2;
int imgSize = mGray.height() * mGray.width();
Scalar zeros = new Scalar(255,1,1, 1);
List<MatOfPoint> contour2 = new ArrayList<MatOfPoint>();
Mat kernel = new Mat(1, 50, CvType.CV_8UC1, Scalar.all(255));
Mat morByte = new Mat();
Mat hierarchy = new Mat();
Rect rectan3 = new Rect();
FeatureDetector detector = FeatureDetector.create(FeatureDetector.MSER);
detector.detect(mGray, keyPoint);
listPoint = keyPoint.toList();
for(int ind = 0; ind < listPoint.size(); ++ind) {
kPoint = listPoint.get(ind);
rectanx1 = (int) (kPoint.pt.x - 0.5 * kPoint.size);
rectany1 = (int) (kPoint.pt.y - 0.5 * kPoint.size);
rectanx2 = (int) (kPoint.size);
rectany2 = (int) (kPoint.size);
if (rectanx1 <= 0) {
rectanx1 = 1;
}
if (rectany1 <= 0) {
rectany1 = 1;
}
if ((rectanx1 + rectanx2) > mGray.width()) {
rectanx2 = mGray.width() - rectanx1;
}
if ((rectany1 + rectany2) > mGray.height()) {
rectany2 = mGray.height() - rectany1;
}
Rect rectant = new Rect(rectanx1, rectany1, rectanx2, rectany2);
try{
Mat roi = new Mat(mask, rectant);
roi.setTo(CONTOUR_COLOR);
}
catch (Exception ex) {
Log.d("mylog", "mat roi error " + ex.getMessage());
}
}
Imgproc.morphologyEx(mask, morByte, Imgproc.MORPH_DILATE, kernel);
Imgproc.findContours(morByte, contour2, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
for(int i = 0; i<contour2.size(); ++i){
rectan3 = Imgproc.boundingRect(contour2.get(i));
if(rectan3.area() > 0.5 * imgSize || rectan3.area()<100 || rectan3.width / rectan3.height < 2){
Mat roi = new Mat(morByte, rectan3);
roi.setTo(zeros);
}else{
Imgproc.rectangle(mRgba, rectan3.br(), rectan3.tl(), CONTOUR_COLOR);
}
}
}
Here's an example of what I get:
My problem is I want to somehow get the text inside the boxes.
I am writing a program which need to detect red circle-alikes from this picture.
I have tried canny edge detection and find contours but none of them find this red "circles". I also tried to convert this to hsv and detect this by color but I couldn't determine good range for this color, maybe background color confuses it?
I put here a piece of my code with my final attempt..
Mat image = new Mat();
image = Imgcodecs.imread("image.jpg");
Mat hsvImage = new Mat();
Mat grayscaleImage = new Mat();
Mat binaryImage = new Mat();
Imgproc.blur(image, image, new Size(1, 1));
Imgproc.cvtColor(image, hsvImage, Imgproc.COLOR_BGR2HSV);
Imgproc.cvtColor(image, grayscaleImage, Imgproc.COLOR_BGR2GRAY);
Imgproc.equalizeHist(grayscaleImage, grayscaleImage);
Imgproc.Canny(grayscaleImage, grayscaleImage, 50, 150, 3,false);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(grayscaleImage.clone(), contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
for (int id=0;id<contours.size();id++){
MatOfPoint2f mop2f = new MatOfPoint2f();
contours.get(id).convertTo(mop2f,CvType.CV_32F);
RotatedRect rectangle = Imgproc.minAreaRect(mop2f);
if (rectangle.boundingRect().width>80)
Imgproc.drawContours(image,contours,id,new Scalar(0,255,0));
}
If you want to process that marked image, you really might want to detect colors. Typically this is done in HSV color-space.
Here is some C++ code to detect "red" color. The result isn't good enough to use findContours yet, but maybe after some dilation. Maybe you can convert the code to Java.
If you want to detect different color, change the line redMask = thresholdHue(hsv, 0, 20, 50, 50); to mask = thresholdHue(hsv, yourWantedHueColorValue, 20, 50, 50);`
// for example to shift a circluar hue-channel
cv::Mat shiftChannel(cv::Mat H, int shift, int maxVal = 180)
{
// CV_8UC1 only!
cv::Mat shiftedH = H.clone();
//int shift = 25; // in openCV hue values go from 0 to 180 (so have to be doubled to get to 0 .. 360) because of byte range from 0 to 255
for (int j = 0; j < shiftedH.rows; ++j)
for (int i = 0; i < shiftedH.cols; ++i)
{
shiftedH.at<unsigned char>(j, i) = (shiftedH.at<unsigned char>(j, i) + shift) % maxVal;
}
return shiftedH;
}
cv::Mat thresholdHue(cv::Mat hsvImage, int hueVal, int range = 30, int minSat = 50, int minValue = 50)
{
// hsvImage must be CV_8UC3 HSV image.
// hue val and range are in openCV's hue range (0 .. 180)
// range shouldnt be bigger than 90, because that's max (all colors), after shifting the hue channel.
// this function will
// 1. shift the hue channel, so that even colors near the border (red color!) will be detectable with same code.
// 2. threshold the hue channel around the value 90 +/- range
cv::Mat mask; // return-value
std::vector<cv::Mat> channels;
cv::split(hsvImage, channels);
int targetHueVal = 180 / 2; // we'll shift the hue-space so that the target val will always be 90 afterwards, no matter which hue value was chosen. This can be important if
int shift = targetHueVal - hueVal;
if (shift < 0) shift += 180;
cv::Mat shiftedHue = shiftChannel(channels[0], shift, 180);
// merge the channels back to hsv image
std::vector<cv::Mat> newChannels;
newChannels.push_back(shiftedHue);
newChannels.push_back(channels[1]);
newChannels.push_back(channels[2]);
cv::Mat shiftedHSV;
cv::merge(newChannels, shiftedHSV);
// threshold
cv::inRange(shiftedHSV, cv::Vec3b(targetHueVal - range, minSat, minValue), cv::Vec3b(targetHueVal + range, 255, 255), mask);
return mask;
}
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/redCircleLikeContours.jpg");
cv::Mat redMask;
cv::Mat hsv;
cv::cvtColor(input, hsv, CV_BGR2HSV);
redMask = thresholdHue(hsv, 0, 20, 50, 50);
cv::imshow("red", redMask);
cv::imshow("input", input);
cv::imwrite("C:/StackOverflow/Output/redCircleLikeContoursMask.png", redMask);
cv::waitKey(0);
return 0;
}
Here's the result:
Here is my code if somebody would want to look :)
public static void main (String args[]){
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat image = new Mat();
image = Imgcodecs.imread("imageorg.jpg");
if ( image == null) System.out.println("Image is fine");
else System.out.println("Wrong path to image");
Mat hsvImage = new Mat();
Imgproc.blur(image, image, new Size(3,3));
Imgproc.cvtColor(image, hsvImage, Imgproc.COLOR_BGR2HSV);
Mat redMask = new Mat();
redMask = thresholdHue(hsvImage,0,20,50,50);
Mat kernel = new Mat();
kernel = Imgproc.getStructuringElement(Imgproc.MORPH_DILATE, new Size(2,2));
Mat dilateMat = new Mat();
Imgproc.dilate(redMask, dilateMat, kernel);
Imgcodecs.imwrite("redCircleLikeContours.png", redMask);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(dilateMat.clone(), contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
List<MatOfPoint> removedContoursList = new ArrayList<MatOfPoint>();
for (int id=0;id<contours.size();id++){
MatOfPoint2f mop2f = new MatOfPoint2f();
contours.get(id).convertTo(mop2f,CvType.CV_32F);
RotatedRect rectangle = Imgproc.minAreaRect(mop2f);
if (rectangle.boundingRect().height<10){
removedContoursList.add(contours.get(id));
System.out.println("removing: "+rectangle.boundingRect());
contours.remove(id);
id--;
}
}
}
public static Mat thresholdHue(Mat hsvImage, int hueVal, int range, int minSat, int minValue)
{
Mat mask = new Mat();
List<Mat> channels = new ArrayList<Mat>();
Core.split(hsvImage, channels);
int targetHueVal = 180 / 2;
int shift = targetHueVal - hueVal;
if (shift < 0) shift += 180;
Mat shiftedHue = shiftChannel(channels.get(0), shift, 180);
List<Mat> newChannels = new ArrayList<Mat>();
newChannels.add(shiftedHue);
newChannels.add(channels.get(1));
newChannels.add(channels.get(2));
Mat shiftedHSV = new Mat();
Core.merge(newChannels, shiftedHSV);
Core.inRange(shiftedHSV, new Scalar(targetHueVal - range, minSat, minValue), new Scalar(targetHueVal + range, 255, 255), mask);
return mask;
}
private static Mat shiftChannel(Mat H, int shift, int maxVal)
{
Mat shiftedH = H.clone();
for (int j = 0; j < shiftedH.rows(); ++j)
for (int i = 0; i < shiftedH.cols(); ++i)
{
shiftedH.put(j, i,(shiftedH.get(j,i)[0] + shift) % maxVal);
}
return shiftedH;
}
I have read from a list of image from a folder and then do cropping and segmented the character from the image. Then the file created is save to another folder with new name. For example the first source image(image1) I have segmented 7 new character images and it goes on with the next image.
My question is how can I regroup these images according to their source image like assigning them (im01-im07 is from source image1) and so on. This is the code:
for (int sf = 0; sf < listOfSrcFFiles.length; sf++) {
File imgFile = listOfSrcFFiles[sf];
String fileName = imgFile.getName();
if (fileName.equals("Thumbs.db")) {
imgFile.delete();
} else {
try{
Mat img_grayROI = Highgui.imread(imgFile.getAbsolutePath(), Highgui.CV_LOAD_IMAGE_GRAYSCALE);
Rect roi = new Rect(300, 200, 450, 200);
Mat cropped = new Mat(img_grayROI, roi);
Imgproc.threshold(cropped, cropped, 0, 255, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU);
Point shift = new Point(0, 150);
Mat result = cropped;
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(result, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
Imgproc.floodFill(result, new Mat(), shift, new Scalar(255, 0, 0));
Core.bitwise_not(result, result);
for (int i = 0; i < contours.size(); i++) {
if (Imgproc.contourArea(contours.get(i)) > 50) {
Rect rect = Imgproc.boundingRect(contours.get(i));
/*if (rect.height > 25 && rect.height < 50 && rect.width > 10 && rect.width < 50) {*/
if (rect.height > 25 && rect.width < 25) {
Core.rectangle(result, rect.tl(), rect.br(), new Scalar(0, 0, 0));
Mat crop = new Mat(result, rect);
Highgui.imwrite(tmpFolder + fSprator + sf + "" + i + ".bmp", crop);
}
}
}
}catch(Exception e){
}
}
}
This is so I can later save them into text file and separated them according to their source image name.
Thank you, any help much appreciated.
The cleanest solution is to create a bean to hold the results.
Eg:
public class TransformResult {
private File input;
private List<File> transformedFiles = new ArrayList<>();
// getters / setters
}
public List<TransformResult> doTransform(File listOfSrcFFiles) {
List<TransformResult> results = new ArrayList<>();
for (File srcFile : listOfSrcFFiles) {
if (...) {
TransformResult result = new TransformResult(srcFile);
for (...) {
if (...) {
File transformed = doSomeTransformation(srcFile);
result.getTransformedFiles().add(transformed);
}
}
results.add(result);
}
}
return results;
}
I have been having some issues getting the outline of the detected object in the correct place, its as if the coordinates are in the wrong place. I have the hessian set to 2000 and I have filtered for matches that are less than 3 times the minimum distance. Any help would be appreciated.
Results from running matching and homography:
Code sample below:
public static void findMatches()
{
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//Load Image 1
Mat img_object = Highgui.imread("./resources/Database/box.png");
//Load Image 2
Mat img_scene = Highgui.imread("./resources/Database/box_in_scene.png");
//Check if either image is null if so exit application
if (img_object == null || img_scene == null)
{
System.exit(0);
}
//Convert Image 1 to greyscale
Mat grayImageobject = new Mat(img_object.rows(), img_object.cols(), img_object.type());
Imgproc.cvtColor(img_object, grayImageobject, Imgproc.COLOR_BGRA2GRAY);
Core.normalize(grayImageobject, grayImageobject, 0, 255, Core.NORM_MINMAX);
//Convert image 2 to greyscale
Mat grayImageScene = new Mat(img_scene.rows(), img_scene.cols(), img_scene.type());
Imgproc.cvtColor(img_scene, grayImageScene, Imgproc.COLOR_BGRA2GRAY);
Core.normalize(grayImageScene, grayImageScene, 0, 255, Core.NORM_MINMAX);
//Create a SURF feature detector
FeatureDetector detector = FeatureDetector.create(4); //4 = SURF
//Cannot input hessian value as normal so we have to write the desired value into a
//file and then read value from file into detector.read
try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("hessian.txt"), "utf-8"))) {
writer.write("%YAML:1.0\nhessianThreshold: 2000.\noctaves:3\noctaveLayers: 4\nupright: 0\n");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
detector.read("hessian.txt");
//Mat of keypoints for object and scene
MatOfKeyPoint keypoints_object = new MatOfKeyPoint();
MatOfKeyPoint keypoints_scene = new MatOfKeyPoint();
//Detect keypoints in scene and object storing them in mat of keypoints
detector.detect(img_object, keypoints_object);
detector.detect(img_scene, keypoints_scene);
DescriptorExtractor extractor = DescriptorExtractor.create(2); //2 = SURF;
Mat descriptor_object = new Mat();
Mat descriptor_scene = new Mat() ;
extractor.compute(img_object, keypoints_object, descriptor_object);
extractor.compute(img_scene, keypoints_scene, descriptor_scene);
DescriptorMatcher matcher = DescriptorMatcher.create(1); // 1 = FLANNBASED
MatOfDMatch matches = new MatOfDMatch();
matcher.match(descriptor_object, descriptor_scene, matches);
List<DMatch> matchesList = matches.toList();
Double max_dist = 0.0;
Double min_dist = 100.0;
for(int i = 0; i < descriptor_object.rows(); i++){
Double dist = (double) matchesList.get(i).distance;
if(dist < min_dist) min_dist = dist;
if(dist > max_dist) max_dist = dist;
}
System.out.println("-- Max dist : " + max_dist);
System.out.println("-- Min dist : " + min_dist);
LinkedList<DMatch> good_matches = new LinkedList<DMatch>();
MatOfDMatch gm = new MatOfDMatch();
for(int i = 0; i < descriptor_object.rows(); i++){
if(matchesList.get(i).distance < 3*min_dist){
good_matches.addLast(matchesList.get(i));
}
}
gm.fromList(good_matches);
Mat img_matches = new Mat();
Features2d.drawMatches(img_object,keypoints_object,img_scene,keypoints_scene, gm, img_matches, new Scalar(255,0,0), new Scalar(0,0,255), new MatOfByte(), 2);
if(good_matches.size() >= 10){
LinkedList<Point> objList = new LinkedList<Point>();
LinkedList<Point> sceneList = new LinkedList<Point>();
List<KeyPoint> keypoints_objectList = keypoints_object.toList();
List<KeyPoint> keypoints_sceneList = keypoints_scene.toList();
for(int i = 0; i<good_matches.size(); i++){
objList.addLast(keypoints_objectList.get(good_matches.get(i).queryIdx).pt);
sceneList.addLast(keypoints_sceneList.get(good_matches.get(i).trainIdx).pt);
}
MatOfPoint2f obj = new MatOfPoint2f();
obj.fromList(objList);
MatOfPoint2f scene = new MatOfPoint2f();
scene.fromList(sceneList);
Mat homography = Calib3d.findHomography(obj, scene);
Mat obj_corners = new Mat(4,1,CvType.CV_32FC2);
Mat scene_corners = new Mat(4,1,CvType.CV_32FC2);
obj_corners.put(0, 0, new double[] {0,0});
obj_corners.put(1, 0, new double[] {img_object.cols(),0});
obj_corners.put(2, 0, new double[] {img_object.cols(),img_object.rows()});
obj_corners.put(3, 0, new double[] {0,img_object.rows()});
//Compute the most probable perspective transformation
//out of several pairs of corresponding points.
//Imgproc.getPerspectiveTransform(obj_corners, scene_corners);
Core.perspectiveTransform(obj_corners,scene_corners, homography);
Core.line(img_matches, new Point(scene_corners.get(0,0)), new Point(scene_corners.get(1,0)), new Scalar(0, 255, 0),4);
Core.line(img_matches, new Point(scene_corners.get(1,0)), new Point(scene_corners.get(2,0)), new Scalar(0, 255, 0),4);
Core.line(img_matches, new Point(scene_corners.get(2,0)), new Point(scene_corners.get(3,0)), new Scalar(0, 255, 0),4);
Core.line(img_matches, new Point(scene_corners.get(3,0)), new Point(scene_corners.get(0,0)), new Scalar(0, 255, 0),4);
Highgui.imwrite("./resources/ImageMatching" + ".jpg", img_matches);
createWindow("Image Matching", "resources/ImageMatching.jpg");
}
else
{
System.out.println("Not enough Matches");
System.exit(0);
}
}
The coordinates are in the correct place, you're simply drawing on the wrong image.
Your coordinates are relative to the second image img_scene . So if you draw your lines only on that image they'll be correct.
If you want to draw the lines on a composed image, where img_scene is translated to the right by the width of the first image img_object, you simply need to add img_object.cols() to the points x coordinates.
For example:
Core.line(img_matches,
new Point(scene_corners.get(0,0)[0] + img_object.cols(), scene_corners.get(0,0)[1]),
new Point(scene_corners.get(1,0)[0] + img_object.cols(), scene_corners.get(1,0)[1]),
new Scalar(0, 255, 0),4);
for the first line, and the same for next 3 lines.