Windows平台编译opencv_contrib for java以及对SIFT(SURF)特征检测算法的使用(附编译好的dll jar包)

引言

近几个月来一直在使用opencv,但无奈于网络上用java语言做图像处理的少之又少,很多时候找不到资料。

最近需要使用opencv的超分和SIFT等特征检测算法,查阅了几天资料踩了一些坑,最终得到了可以使用的opencv_contrib的dll以及jar包,随后会附赠到文章中(某隔壁网站拿一些没法用的资源收费简直是损阴德。)

准备工具

  • VS2019(请务必选择2019,使用2022编译时可能出现未知错误)
  • opencv和opencv_contrib的包(OpenCV (github.com))
  • cmake安装并添加环境变量
  • ant安装并添加环境变量
  • python环境安装并添加环境变量

编译

  • 打开cmake选择opencv的源代码以及编译后存放的文件夹
  • image.png

  • 点击Configure选择VS2019,确认后开始编译
    • 由于网络问题,opencv源文件中的.cache文件夹中需要下载的文件下载失败。

    可以直接查看cmake的错误日志,前往github下载所需下载的文件放入.cache文件夹中即可正常编译

    附上4.1.2所需的.cache链接
    链接:pan.baidu.com/s/1V-yUfMW_…
    提取码:hgqd

    • 若选择其他版本的VS,可能出现OpenCV does not recognize MSVC_VERSION “1933“等错误Visual Studio版本号、MSVC版本、工具集版本号_查看msvc版本-CSDN博客
      查阅VS对应的MSVC版本,在opencv/cmake/OpenCVDetectCXXCompiler.cmake文件中添加如图所示语句即可解决。

    image.png

  • 勾选cmake中的以下选项,点击generate
    • 搜索java,勾选BUILD_JAVA、BUILD_opencv_java、BUILD_opencv_java_bindings_generator
    • 搜索nonf,勾选OPENCV_ENABLE_NONFREE(对SIFT等算法的支持)
    • 搜索share,取消勾选BUILD_SHARED_LIBS(让生成的dll都在一个文件中)
    • 搜索ant,在ANT_EXECUTABLE行添加ant.bat批处理文件的路径
    • 搜索module,在OPENCV_EXTRA_MODUELES_PATH行添加下载的opencv_contrib源代码中的module文件夹路径

    可能遇到的问题:下载超时

    在opencv_contrib/modules/face/CMakeList.txt中将下载链接改成如下图所示raw.sevencdn.com

    image.png

  • 等待编译完成后,点击Open Project
  • 打开VS2019,重新生成解决方案

    image.png

    待编译完成后,在CMakeTargets中的INSTALL-仅用于项目-仅重新生成。等待编译结束即可在Cmake中选择的目标文件夹下的install目录中找到java所需相关包

    image.png

    附赠链接,opencv4.1.2包含conrtib模块的jar包和dll拓展文件

    链接:pan.baidu.com/s/16fZfbpah…
    提取码:s4y4
    --来自百度网盘超级会员V6的分享

    SIFT算法的使用

    不得不说用JAVA写这个的资料是更少……在这里我只放核心模块,具体可以直接执行的代码随后会放参考链接,按照链接博客里的代码稍微改动就可以直接使用

    其中nndrRatio博主填写的是0.7f,算是一个效果比较好的值,各位可以自行尝试调整。

    public void matchImage(BufferedImage templateImageB, BufferedImage originalImageB) {
    Mat resT = new Mat();
    Mat resO = new Mat();
    //即当detector 又当Detector
    SIFT sift = SIFT.create();
    Mat templateImage = getMatify(templateImageB);
    Mat originalImage = getMatify(originalImageB);
    MatOfKeyPoint templateKeyPoints = new MatOfKeyPoint();
    MatOfKeyPoint originalKeyPoints = new MatOfKeyPoint();
    //获取模板图的特征点
    sift.detect(templateImage, templateKeyPoints);
    sift.detect(originalImage, originalKeyPoints);
    sift.compute(templateImage, templateKeyPoints, resT);
    sift.compute(originalImage, originalKeyPoints, resO);
    List matches = new LinkedList();
    DescriptorMatcher descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
    System.out.println("寻找最佳匹配");
    printPic("ptest", templateImage);
    printPic("ptesO", originalImage);
    printPic("test", resT);
    printPic("tesO", resO);
    /**
    * knnMatch方法的作用就是在给定特征描述集合中寻找最佳匹配
    * 使用KNN-matching算法,令K=2,则每个match得到两个最接近的descriptor,然后计算最接近距离和次接近距离之间的比值,当比值大于既定值时,才作为最终match。
    */
    descriptorMatcher.knnMatch(resT, resO, matches, 2);
    System.out.println("计算匹配结果");
    LinkedList goodMatchesList = new LinkedList();
    //对匹配结果进行筛选,依据distance进行筛选
    matches.forEach(match -> {
    DMatch[] dmatcharray = match.toArray();
    DMatch m1 = dmatcharray[0];
    DMatch m2 = dmatcharray[1];
    if (m1.distance = 4) {
    System.out.println("模板图在原图匹配成功!");
    List templateKeyPointList = templateKeyPoints.toList();
    List originalKeyPointList = originalKeyPoints.toList();
    LinkedList objectPoints = new LinkedList();
    LinkedList scenePoints = new LinkedList();
    goodMatchesList.forEach(goodMatch -> {
    objectPoints.addLast(templateKeyPointList.get(goodMatch.queryIdx).pt);
    scenePoints.addLast(originalKeyPointList.get(goodMatch.trainIdx).pt);
    });
    MatOfPoint2f objMatOfPoint2f = new MatOfPoint2f();
    objMatOfPoint2f.fromList(objectPoints);
    MatOfPoint2f scnMatOfPoint2f = new MatOfPoint2f();
    scnMatOfPoint2f.fromList(scenePoints);
    //使用 findHomography 寻找匹配上的关键点的变换
    Mat homography = Calib3d.findHomography(objMatOfPoint2f, scnMatOfPoint2f, Calib3d.RANSAC, 3);
    /**
    * 透视变换(Perspective Transformation)是将图片投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。
    */
    Mat templateCorners = new Mat(4, 1, CvType.CV_32FC2);
    Mat templateTransformResult = new Mat(4, 1, CvType.CV_32FC2);
    templateCorners.put(0, 0, new double[]{0, 0});
    templateCorners.put(1, 0, new double[]{templateImage.cols(), 0});
    templateCorners.put(2, 0, new double[]{templateImage.cols(), templateImage.rows()});
    templateCorners.put(3, 0, new double[]{0, templateImage.rows()});
    //使用 perspectiveTransform 将模板图进行透视变以矫正图象得到标准图片
    Core.perspectiveTransform(templateCorners, templateTransformResult, homography);
    //矩形四个顶点 匹配的图片经过旋转之后就这个矩形的四个点的位置就不是正常的abcd了
    double[] pointA = templateTransformResult.get(0, 0);
    double[] pointB = templateTransformResult.get(1, 0);
    double[] pointC = templateTransformResult.get(2, 0);
    double[] pointD = templateTransformResult.get(3, 0);
    //指定取得数组子集的范围
    // int rowStart = (int) pointA[1];
    // int rowEnd = (int) pointC[1];
    // int colStart = (int) pointD[0];
    // int colEnd = (int) pointB[0];
    //rowStart, rowEnd, colStart, colEnd 好像必须左上右下 没必要从原图扣下来模板图了
    // Mat subMat = originalImage.submat(rowStart, rowEnd, colStart, colEnd);
    // printPic("yppt", subMat);
    //将匹配的图像用用四条线框出来
    Imgproc.rectangle(originalImage, new Point(pointA), new Point(pointC), new Scalar(0, 255, 0));
    /* Core.line(originalImage, new Point(pointA), new Point(pointB), new Scalar(0, 255, 0), 4);//上 A->B
    Core.line(originalImage, new Point(pointB), new Point(pointC), new Scalar(0, 255, 0), 4);//右 B->C
    Core.line(originalImage, new Point(pointC), new Point(pointD), new Scalar(0, 255, 0), 4);//下 C->D
    Core.line(originalImage, new Point(pointD), new Point(pointA), new Scalar(0, 255, 0), 4);//左 D->A*/
    MatOfDMatch goodMatches = new MatOfDMatch();
    goodMatches.fromList(goodMatchesList);
    Mat matchOutput = new Mat(originalImage.rows() * 2, originalImage.cols() * 2, Imgcodecs.IMREAD_COLOR);
    Features2d.drawMatches(templateImage, templateKeyPoints, originalImage, originalKeyPoints, goodMatches, matchOutput, new Scalar(0, 255, 0), new Scalar(255, 0, 0), new MatOfByte(), 2);
    Features2d.drawMatches(templateImage, templateKeyPoints, originalImage, originalKeyPoints, goodMatches, matchOutput, new Scalar(0, 255, 0), new Scalar(255, 0, 0), new MatOfByte(), 2);
    printPic("ppgc", matchOutput);
    printPic("ytwz", originalImage);
    } else {
    System.out.println("模板图不在原图中!");
    }
    printPic("模板特征点", resT);
    }
    public void printPic(String name, Mat pre) {
    Imgcodecs.imwrite(name + ".jpg", pre);
    }
    /**
    * 尝试把BufferedImage转换为Mat
    *
    * @param im
    * @return
    */
    public Mat getMatify(BufferedImage im) {
    BufferedImage bufferedImage = toBufferedImageOfType(im, BufferedImage.TYPE_3BYTE_BGR);
    //将bufferedimage转换为字节数组
    byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
    // byte[] pixels = ((DataBufferByte) im.getRaster().getDataBuffer()).getData();
    Mat image = new Mat(bufferedImage.getHeight(), bufferedImage.getWidth(), CvType.CV_8UC3);
    image.put(0, 0, pixels);
    return image;
    }

    参考博客

    【一】win10下编译opencv4.1.2,opencv_contrib for java_win10编译opencv cuda cudnn支持java_东街猫儿的博客-CSDN博客

    Visual Studio版本号、MSVC版本、工具集版本号_查看msvc版本-CSDN博客

    openCV4.4.0 基于SIFT特征值的图像匹配【java】。。。。。搞到吐_public picpoint_Sakura小败狗的博客-CSDN博客

    结语

    整理不易,觉得有用的话麻烦点点赞吧。
    再次吐槽某些拿国外资源以及无法使用的资源进行付费的行为。