Android中的OpenCV图像比较和相似性。

15

我是一个学习OpenCV的人。 我正在尝试图像比较。 我使用了OpenCV 2.4.13.3。我有这两个图片1.jpgcam1.jpg

图片描述 图片描述

当我在OpenCV中使用以下命令时:

File sdCard = Environment.getExternalStorageDirectory();
String path1, path2;
path1 = sdCard.getAbsolutePath() + "/1.jpg";
path2 = sdCard.getAbsolutePath() + "/cam1.jpg";

FeatureDetector detector = FeatureDetector.create(FeatureDetector.ORB);
DescriptorExtractor extractor = DescriptorExtractor.create(DescriptorExtractor.BRIEF);
DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);

Mat img1 = Highgui.imread(path1);
Mat img2 = Highgui.imread(path2);

Mat descriptors1 = new Mat();
MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
detector.detect(img1, keypoints1);
extractor.compute(img1, keypoints1, descriptors1);

//second image
// Mat img2 = Imgcodecs.imread(path2);
Mat descriptors2 = new Mat();
MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
detector.detect(img2, keypoints2);
extractor.compute(img2, keypoints2, descriptors2);


//matcher image descriptors
MatOfDMatch matches = new MatOfDMatch();
matcher.match(descriptors1,descriptors2,matches);

// Filter matches by distance
MatOfDMatch filtered = filterMatchesByDistance(matches);

int total = (int) matches.size().height;
int Match= (int) filtered.size().height;
Log.d("LOG", "total:" + total + " Match:"+Match);

方法 filterMatchesByDistance

    static MatOfDMatch filterMatchesByDistance(MatOfDMatch matches){
    List<DMatch> matches_original = matches.toList();
    List<DMatch> matches_filtered = new ArrayList<DMatch>();

    int DIST_LIMIT = 30;
    // Check all the matches distance and if it passes add to list of filtered matches
    Log.d("DISTFILTER", "ORG SIZE:" + matches_original.size() + "");
    for (int i = 0; i < matches_original.size(); i++) {
        DMatch d = matches_original.get(i);
        if (Math.abs(d.distance) <= DIST_LIMIT) {
            matches_filtered.add(d);
        }
    }
    Log.d("DISTFILTER", "FIL SIZE:" + matches_filtered.size() + "");

    MatOfDMatch mat = new MatOfDMatch();
    mat.fromList(matches_filtered);
    return mat;
}

日志

total:122 Match:30

从日志中可以看到比赛得分为30分。
但是我们可以看到两张图片具有相同的视觉元素 (in)。
如何使用openCV获得匹配度为90?
如果有人能提供代码片段将会很棒。
如果使用openCV不可能实现,那么我们可以寻找哪些其他替代方案?


这似乎是一个关于OpenCV和/或图像识别教程的问题。我会链接到另一个SE小组,阅读像这样的帖子:https://dsp.stackexchange.com/questions/17846/template-matching-or-object-recognition 可能为进一步的研究提供适当的背景。 - Morrison Chang
2
为什么不更改阈值“DIST_LIMIT”?如果30个足够了,获取更多匹配的目的是什么? - Rick M.
3
这张图片对ORB/SIFT描述符来说似乎很有挑战性......它基本上只有两种颜色,而大多数描述符(如果不是全部)都会出现在边缘周围,这些边缘与许多其他边缘相似,因此您会得到很多误匹配(最佳匹配距离实际比您想象的要远),当按距离过滤时,只会剩下最靠近它的描述符。也许您可以尝试另一种方法,更像是模式匹配之类的。 - api55
2
你检查过这个了吗?使用你的图像可以得到很好的结果。 https://github.com/opencv/opencv/blob/master/samples/cpp/matchmethod_orb_akaze_brisk.cpp - zindarod
2
我同意@api55的观点,特征检测/匹配并不适用于这种图像,因为它们不会产生足够的唯一关键点。您可以尝试使用模式匹配,这可能会更加成功,但不幸的是,它对旋转不太稳健。此外,请勿明确要求代码,SO不是编码服务。 - Elouarn Laine
显示剩余2条评论
1个回答

7
但是我们可以看到这两个图像都有相同的视觉元素("in")。因此,我们应该比较的不是整个图像,而是其中的"相同视觉元素"。如果您不仅仅比较"模板"和"相机"图像本身,而是以相同的方式处理(例如转换为二进制黑/白)"模板"和"相机"图像,您可以更改Match值。例如,在"模板"和"相机"图像上找到蓝色(模板标志的背景)正方形,并比较这些正方形(感兴趣区域)。代码可能如下所示:
Bitmap bmImageTemplate = <get your template image Bitmap>;
Bitmap bmTemplate = findLogo(bmImageTemplate);  // process template image

Bitmap bmImage = <get your camera image Bitmap>;
Bitmap bmLogo = findLogo(bmImage); // process camera image same way

compareBitmaps(bmTemplate, bmLogo);

where

private Bitmap findLogo(Bitmap sourceBitmap) {
    Bitmap roiBitmap = null;
    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<Rect> squares = 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 > 400) {
            Point rotated_rect_points[] = new Point[4];
            boundingRect.points(rotated_rect_points);

            Rect rect = Imgproc.boundingRect(new MatOfPoint(rotated_rect_points));
            double aspectRatio = rect.width > rect.height ?
                    (double) rect.height / (double) rect.width : (double) rect.width / (double) rect.height;
            if (aspectRatio >= 0.9) {
                squares.add(rect);
            }
        }
    }

    Mat logoMat = extractSquareMat(roiTmp, getBiggestSquare(squares));

    roiBitmap = Bitmap.createBitmap(logoMat.cols(), logoMat.rows(), Bitmap.Config.ARGB_8888);
    Utils.matToBitmap(logoMat, roiBitmap);
    return roiBitmap;
}

extractSquareMat() 方法只是从整个图像中提取感兴趣的区域(标志)。

public static Mat extractSquareMat(Mat sourceMat, Rect rect) {
    Mat squareMat = null;
    int padding = 50;

    if (rect != null) {
        Rect truncatedRect = new Rect((int) rect.tl().x + padding, (int) rect.tl().y + padding,
                rect.width - 2 * padding, rect.height - 2 * padding);
        squareMat = new Mat(sourceMat, truncatedRect);
    }

    return squareMat ;
}

compareBitmaps() 只是你的代码的一个包装器:

private void compareBitmaps(Bitmap bitmap1, Bitmap bitmap2) {
    Mat mat1 = new Mat(bitmap1.getWidth(), bitmap1.getHeight(), CvType.CV_8UC3);
    Utils.bitmapToMat(bitmap1, mat1);

    Mat mat2 = new Mat(bitmap2.getWidth(), bitmap2.getHeight(), CvType.CV_8UC3);
    Utils.bitmapToMat(bitmap2, mat2);

    compareMats(mat1, mat2);
}

您的代码作为方法:

private void compareMats(Mat img1, Mat img2) {
    FeatureDetector detector = FeatureDetector.create(FeatureDetector.ORB);
    DescriptorExtractor extractor = DescriptorExtractor.create(DescriptorExtractor.BRIEF);
    DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);

    Mat descriptors1 = new Mat();
    MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
    detector.detect(img1, keypoints1);
    extractor.compute(img1, keypoints1, descriptors1);

    //second image
    // Mat img2 = Imgcodecs.imread(path2);
    Mat descriptors2 = new Mat();
    MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
    detector.detect(img2, keypoints2);
    extractor.compute(img2, keypoints2, descriptors2);


    //matcher image descriptors
    MatOfDMatch matches = new MatOfDMatch();
    matcher.match(descriptors1,descriptors2,matches);

    // Filter matches by distance
    MatOfDMatch filtered = filterMatchesByDistance(matches);

    int total = (int) matches.size().height;
    int Match= (int) filtered.size().height;
    Log.d("LOG", "total:" + total + " Match:" + Match);
}

static MatOfDMatch filterMatchesByDistance(MatOfDMatch matches){
    List<DMatch> matches_original = matches.toList();
    List<DMatch> matches_filtered = new ArrayList<DMatch>();

    int DIST_LIMIT = 30;
    // Check all the matches distance and if it passes add to list of filtered matches
    Log.d("DISTFILTER", "ORG SIZE:" + matches_original.size() + "");
    for (int i = 0; i < matches_original.size(); i++) {
        DMatch d = matches_original.get(i);
        if (Math.abs(d.distance) <= DIST_LIMIT) {
            matches_filtered.add(d);
        }
    }
    Log.d("DISTFILTER", "FIL SIZE:" + matches_filtered.size() + "");

    MatOfDMatch mat = new MatOfDMatch();
    mat.fromList(matches_filtered);
    return mat;
}

从您的问题结果保存的缩小(缩放为50%)图像的结果如下:

D/DISTFILTER: ORG SIZE:237
D/DISTFILTER: FIL SIZE:230
D/LOG: total:237 Match:230

注意!这只是一个快速而简单的示例,仅用于演示给定模板的方法。

附注:getBiggestSquare() 可以像这样(基于面积比较):

public static Rect getBiggestSquare(List<Rect> squares) {
    Rect biggestSquare = null;

    if (squares != null && squares.size() >= 1) {
        Rect square;
        double maxArea;
        int ixMaxArea = 0;

        square = squares.get(ixMaxArea);
        maxArea = square.area();

        for (int ix = 1; ix < squares.size(); ix++) {
            square = squares.get(ix);
            if (square.area() > maxArea) {
                maxArea = square.area();
                ixMaxArea = ix;
            }
        }

        biggestSquare = squares.get(ixMaxArea);
    }

    return biggestSquare;
}

这只适用于蓝色标志。其他标志呢?我有10-12个标志。 - Kanaiya Katarmal
大多数情况下,可以创建ROI提取规则,并逐个循环测试每个标志的相似度。但是如果您有许多具有复杂形状(例如带有渐变的图片)的标志(模式),更好的方法是使用基于机器学习的方法。请参考此教程或者那个教程。 - Andrii Omelchenko
什么是 getBiggestSquare 方法? - Code Kadiya
@AndriiOmelchenko 请展示函数:getBiggestSquare()。 - ssPerman01
@CodeKadiya请看一下P.S. - Andrii Omelchenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接