引言
近几个月来一直在使用opencv,但无奈于网络上用java语言做图像处理的少之又少,很多时候找不到资料。
最近需要使用opencv的超分和SIFT等特征检测算法,查阅了几天资料踩了一些坑,最终得到了可以使用的opencv_contrib的dll以及jar包,随后会附赠到文章中(某隔壁网站拿一些没法用的资源收费简直是损阴德。)
准备工具
- VS2019(请务必选择2019,使用2022编译时可能出现未知错误)
- opencv和opencv_contrib的包(OpenCV (github.com))
- cmake安装并添加环境变量
- ant安装并添加环境变量
- python环境安装并添加环境变量
编译
- 打开cmake选择opencv的源代码以及编译后存放的文件夹
- 点击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文件中添加如图所示语句即可解决。
- 勾选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
- 等待编译完成后,点击Open Project
打开VS2019,重新生成解决方案
待编译完成后,在CMakeTargets中的INSTALL-仅用于项目-仅重新生成。等待编译结束即可在Cmake中选择的目标文件夹下的install目录中找到java所需相关包
附赠链接,opencv4.1.2包含conrtib模块的jar包和dll拓展文件
链接:pan.baidu.com/s/16fZfbpah... 提取码:s4y4 --来自百度网盘超级会员V6的分享
SIFT算法的使用
不得不说用JAVA写这个的资料是更少......在这里我只放核心模块,具体可以直接执行的代码随后会放参考链接,按照链接博客里的代码稍微改动就可以直接使用
其中nndrRatio博主填写的是0.7f,算是一个效果比较好的值,各位可以自行尝试调整。
java
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<MatOfDMatch> 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<DMatch> goodMatchesList = new LinkedList();
//对匹配结果进行筛选,依据distance进行筛选
matches.forEach(match -> {
DMatch[] dmatcharray = match.toArray();
DMatch m1 = dmatcharray[0];
DMatch m2 = dmatcharray[1];
if (m1.distance <= m2.distance * nndrRatio) {
goodMatchesList.addLast(m1);
}
});
matchesPointCount = goodMatchesList.size();
//当匹配后的特征点大于等于 4 个,则认为模板图在原图中,该值可以自行调整
if (matchesPointCount >= 4) {
System.out.println("模板图在原图匹配成功!");
List<KeyPoint> templateKeyPointList = templateKeyPoints.toList();
List<KeyPoint> originalKeyPointList = originalKeyPoints.toList();
LinkedList<Point> objectPoints = new LinkedList();
LinkedList<Point> 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博客
结语
整理不易,觉得有用的话麻烦点点赞吧。 再次吐槽某些拿国外资源以及无法使用的资源进行付费的行为。