当前位置:   article > 正文

基于AI的计算机视觉识别在Java项目中的使用 (二) —— OpenCV的使用_java视觉识别特征

java视觉识别特征

计算机视觉领域的应用一般都会以OpenCV为基础。产品里我们使用OpenCV来做图像的读写、色彩变化、图像截取和拼接、图像畸变矫正、轮廓分析,另外还有图像矩阵的均值、标准差的计算和图像矩阵加减乘除运算等。

OpenCV在Java项目中的配置

服务端用的SpringBoot,所以需要在SpringBoot项目里使用OpenCV。OpenCV没有Java版本的实现,但提供了JNI来调用 C 编译出的动态库。缺点是引入麻烦,优点是 C语言对于包含大量矩阵计算的图形操作执行速度很快。

Windows

开发环境用的windows10,首先下载安装windows版本的OpenCV,然后在安装目录 opencv\build\java 下找到opencv-xxx.jar 和 opencv_javaxxx.dll (xxx是版本号)两个文件。

jar文件可copy到项目的resource目录中,我放到 src\main\resources\lib.opencv目录下。这个jar里面封装了对底层 C 方法调用接口,在Windows和Linux环境下通用。

另外的 opencv_javaxxx.dll 文件需要复制到 C:\Windows\System32 目录下。要将jar添加到项目依赖的库中,还需要在pom.xml中增加 dependency 配置:

  1.   <dependencies>
  2.     ...
  3.     <!-- opencv -->
  4.     <dependency>
  5.         <groupId>org</groupId>
  6.         <artifactId>opencv</artifactId>
  7.         <version>4.5.3</version>
  8.         <scope>system</scope>
  9.         <systemPath>${pom.basedir}/src/main/resources/lib.opencv/opencv-453.jar</systemPath>
  10.     </dependency>
  11.     ...
  12.   </dependencies>
  13.   ...

Docker(Linux)

部署用的Docker,因为没有找到版本合适的Java+OpenCV的Docker Image,所以只能用一个Java Docker Image作为基础,在基础 Image 上再安装OpenCV。

Linux下安装OpenCV需要用cmake对OpenCV的源码做编译,稍微麻烦一些。索性我绕过这个环节找了一个 sh脚本 搞定(这个脚本里可以设置安装版本等参数)。

另外,将SpringBoot项目打包为Docker Image时, 需要在SpringBoot项目的pom.xml配置文件中增加如下设置,让批处理将指定的 jar 文件打包到的项目依赖的 lib 目录下。

  1.   ...
  2.   <build>
  3.      ...
  4.         <resources>
  5.             <resource>
  6.                 <directory>src/main/resources</directory>
  7.                 <targetPath>.</targetPath>
  8.                 <includes>
  9.                     <include>**/*</include>
  10.                 </includes>
  11.             </resource>
  12.             <resource>
  13.                 <directory>src/main/resources/lib.opencv</directory>
  14.                 <targetPath>BOOT-INF/lib/</targetPath>
  15.                 <includes>
  16.                     <include>**/*.jar</include>
  17.                 </includes>
  18.             </resource>
  19.             <resource>
  20.                 <directory>src/main/resources</directory>
  21.                 <targetPath>BOOT-INF/classes/</targetPath>
  22.                 <includes>
  23.                     <include>**/*.properties</include>
  24.                 </includes>
  25.             </resource>
  26.         </resources>
  27.       ...    
  28.   </build>

加载动态库

OpenCV在Java中是通过JNI调用,在调用之前还要加载对应的动态库(Windows下是dll文件,Linux下是so文件)。 可以在CvService中以静态代码块的方式在初始化类时做JNI加载。

  1. import org.opencv.core.Core;
  2. public class CvService {
  3.   static {
  4.       System.loadLibrary(
  5.         Core.NATIVE_LIBRARY_NAME
  6.       );
  7.   }
  8.   ...
  9. }

OpenCV的常用 Java API

下面简要介绍Java中OpenCV的调用方式。 首先随手拍张照片作为演示图片。

Mat

首先在OpenCV中,图像数据是通过Mat矩阵类来包装的。

用 Imgcodecs 读写图像

  1. import org.opencv.imgproc.Imgcodecs;
  2. /**
  3. 从文件路径读图像
  4. */
  5. String path = "D:\\demo.jpg";
  6. Mat img = Imgcodecs.imread(path);
  7. /** 
  8. 图像写到指定路径 
  9. */
  10. String savePath = "D:\\demo_bak.jpg";
  11. Imgcodecs.imwrite(savePath, img);

用 Imgproc 进行简单图像处理

图形灰度化

  1. import org.opencv.imgproc.Imgproc;
  2. /**
  3.  * 创建一个空的Mat对象,用来存储图片处理中间结果
  4.  */
  5. Mat grayImg = new Mat();
  6. /**
  7.  * 将图像img由BGR转为灰度图,结果保存到tempImg
  8.  */
  9. Imgproc.cvtColor(
  10.   img, grayImg, Imgproc.COLOR_BGR2GRAY
  11. );
  12. Imgcodecs.imwrite(
  13.   "D:\\card_test\\process\\gray.jpg"
  14.   grayImg);

图形二值化

  1. /**
  2.  * 高斯滤波降噪
  3.  */
  4. Mat blurImg = new Mat();
  5. Imgproc.GaussianBlur(
  6.   grayImg, 
  7.   blurImg, 
  8.   new Size(3,3), 22
  9. );
  10. /**
  11.  * 使用自适应移动平均阈值法
  12.  * 继续对图像进行黑白二值化处理
  13.  */
  14. Mat binaryImg = new Mat();
  15. Imgproc.adaptiveThreshold(
  16.   blurImg, 
  17.   binaryImg, 
  18.   255
  19.   Imgproc.ADAPTIVE_THRESH_MEAN_C, 
  20.   Imgproc.THRESH_BINARY
  21.   45,  
  22.   11
  23. );
  24. Imgcodecs.imwrite(
  25.   "D:\\card_test\\process\\binary.jpg"
  26.   binaryImg);

Canny边缘检测

  1. Mat cannyImg = new Mat();
  2. Imgproc.Canny(
  3.  binaryImg, 
  4.  cannyImg, 
  5.  20
  6.  60
  7.  3
  8.  false);
  9. Imgcodecs.imwrite(
  10.   "D:\\card_test\\process\\Canny.jpg"
  11.   cannyImg);

膨胀增强边缘

  1. Mat dilateImg = new Mat();
  2. Imgproc.dilate(
  3.   cannyImg, 
  4.   dilateImg, 
  5.   new Mat(), 
  6.   new Point(-1,-1), 
  7.   31
  8.   new Scalar(1));
  9. Imgcodecs.imwrite(
  10.   "D:\\card_test\\process\\dilateImg.jpg"
  11.   dilateImg);

轮廓查找

  1. /**
  2.  * 从图片中搜索所有轮廓
  3.  */
  4. List<MatOfPoint> contours = new ArrayList();
  5. Mat hierarchy = new Mat();
  6. Imgproc.findContours(
  7.   binaryImg, 
  8.   contours, 
  9.   hierarchy, 
  10.   Imgproc.RETR_EXTERNAL
  11.   Imgproc.CHAIN_APPROX_SIMPLE
  12. );
  13. /**
  14.  * 从所有轮廓中找到最大的轮廓
  15.  */
  16. int maxIdx = 0;
  17. double maxSize = 0;
  18. for (int i = 0; i < contours.size(); i++) {
  19.     double size = Imgproc.contourArea(
  20.       contours.get(i)
  21.     );
  22.     if(maxSize < size) {
  23.         maxIdx = i;
  24.         maxSize = size;
  25.     }
  26. }
  27. MatOfPoint maxContour = contours.get(maxIdx);
  28. /**
  29.  * 将最大的轮廓绘制在原始图片上
  30.  */
  31. Mat imgCopy = img.clone();
  32. Imgproc.drawContours(
  33.   imgCopy, 
  34.   contours, 
  35.   maxIdx, 
  36.   new Scalar(00255), 
  37.   4
  38.   LINE_8
  39. );
  40. Imgcodecs.imwrite(
  41.   "D:\\card_test\\process\\contour.jpg"
  42.   imgCopy);

外接矩形

  1. /**
  2.  * 找到轮廓的外接矩形
  3.  */
  4. Rect rect = Imgproc.boundingRect(maxContour);
  5. /**
  6.  * 在原图上绘制出外接矩形
  7.  */
  8. Mat rectImg = img.clone();
  9. Imgproc.rectangle(
  10.         rectImg,
  11.         rect,
  12.         new Scalar(00255),
  13.         2,
  14.         Imgproc.LINE_8
  15. );
  16. Imgcodecs.imwrite(
  17.         "D:\\card_test\\process\\rect.jpg",
  18.         rectImg);

综合应用(切取图中的轮廓并矫正为矩形)

切图效果

  1. /**
  2.  * 计算边框的凸包
  3.  */
  4. MatOfInt hull = new MatOfInt();
  5. Imgproc.convexHull(maxContour, hull);
  6. /**
  7.  * 得到凸包对应的轮廓点
  8.  */
  9. Point[] contourPoints = maxContour.toArray();
  10. int[] indices = hull.toArray();
  11. List<Point> newPoints = new ArrayList();
  12. for (int index : indices) {
  13.     newPoints.add(contourPoints[index]);
  14. }
  15. MatOfPoint2f contourHull = new MatOfPoint2f();
  16. contourHull.fromList(newPoints);
  17. /**
  18.  * 使用轮廓周长的1%作为阈值
  19.  */
  20. double thresholdL = Imgproc.arcLength(contourHull, true* 0.01;
  21. /**
  22.  * 用多边形拟合凸包边框,取得拟合多边形的所有顶点
  23.  */
  24. MatOfPoint2f approx = new MatOfPoint2f();
  25. approx.convertTo(approx, CvType.CV_32F);
  26. Imgproc.approxPolyDP(contourHull, approx, thresholdL, true);
  27. List<Point> points = approx.toList();
  28. /**
  29.  * 找到所有顶点连线中,边长大于 4 * thresholdL的四条边作为四边形物体的四条边
  30.  */
  31. List<double[]> lines = new ArrayList();
  32. for (int i = 0; i < points.size(); i++) {
  33.     Point p1 = points.get(i);
  34.     Point p2 = points.get((i + 1) % points.size());
  35.     if (getSpacePointToPoint(p1, p2> 4 * thresholdL) {
  36.         lines.add(new double[]{p1.x, p1.y, p2.x, p2.y});
  37.     }
  38. }
  39. /**
  40.  * 计算出这四条边中 相邻两条边的交点,即物体的四个顶点
  41.  */
  42. List<Point> corners = new ArrayList();
  43. for (int i = 0; i < lines.size(); i++) {
  44.     Point corner = computeIntersect(
  45.             lines.get(i),
  46.             lines.get((i + 1) % lines.size()));
  47.     corners.add(corner);
  48. }
  49. /**
  50.  * 对顶点顺时针排序
  51.  */
  52. sortCorners(corners);
  53. /**
  54.  * 使用第12点距离作为宽,第14点间距离作为高
  55.  */
  56. Point p0 = corners.get(0);
  57. Point p1 = corners.get(1);
  58. Point p2 = corners.get(2);
  59. Point p3 = corners.get(3);
  60. double imgWidth = getSpacePointToPoint(p0, p1);
  61. double imgHeight = getSpacePointToPoint(p3, p0);
  62. /**
  63.  * 定义存放切图的矩阵
  64.  */
  65. Mat dstMat = Mat.zeros(
  66.         (int) imgHeight,
  67.         (int) imgWidth,
  68.         CvType.CV_8UC3);
  69. /**
  70.  * 定义图形矫正源的四个顶点
  71.  */
  72. MatOfPoint2f src = new MatOfPoint2f(p0, p1, p2, p3);
  73. /**
  74.  * 定义图形矫正目标的四个顶点
  75.  */
  76. MatOfPoint2f dst = new MatOfPoint2f(
  77.         new Point(00),
  78.         new Point(imgWidth, 0),
  79.         new Point(imgWidth, imgHeight),
  80.         new Point(0, imgHeight));
  81. /**
  82.  * 定义透视变换矩阵并进行变换操作
  83.  */
  84. Mat trans = Imgproc.getPerspectiveTransform(src, dst);
  85. Imgproc.warpPerspective(img, dstMat, trans, dstMat.size());
  86. Imgcodecs.imwrite(
  87.         "D:\\card_test\\process\\cutMat.jpg",
  88.         dstMat);

上述过程中用到得一些方法实现

计算点到点的距离

  1. /**
  2.  * 点到点的距离
  3.  *
  4.  * @param p1
  5.  * @param p2
  6.  * @return
  7.  */
  8. public double getSpacePointToPoint(Point p1
  9.         Point p2) {
  10.     if (p1 == null || p2 == null) {
  11.         return 0;
  12.     }
  13.     double a = p1.x - p2.x;
  14.     double b = p1.y - p2.y;
  15.     return Math.sqrt(a * a + b * b);
  16. }

计算两直线的交点

  1. /**
  2.  * 计算两直线的交点
  3.  *
  4.  * @param a
  5.  * @param b
  6.  * @return
  7.  */
  8. public Point computeIntersect(double[] a, double[] b) {
  9.     if (a.length != 4 || b.length != 4)
  10.         throw new ClassFormatError();
  11.     double x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3], x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];
  12.     double d = ((x1 - x2* (y3 - y4)) - ((y1 - y2* (x3 - x4));
  13.     if (d != 0) {
  14.         Point pt = new Point();
  15.         pt.x = ((x1 * y2 - y1 * x2* (x3 - x4) - (x1 - x2* (x3 * y4 - y3 * x4)) / d;
  16.         pt.y = ((x1 * y2 - y1 * x2* (y3 - y4) - (y1 - y2* (x3 * y4 - y3 * x4)) / d;
  17.         return pt;
  18.     } else {
  19.         return new Point(-1, -1);
  20.     }
  21. }

对多个点按顺时针排序(右上、左上、左下、右下)

  1. /**
  2.  * 对多个点按顺时针排序(右上、左上、左下、右下)
  3.  *
  4.  * @param corners
  5.  */
  6. public void sortCorners(List<Point> corners) {
  7.     if (corners.size() == 0return;
  8.     // 首先取得矩形中心点的 x,y值
  9.     int centerX = 0, centerY = 0;
  10.     for (Point point : corners) {
  11.         centerX += point.x;
  12.         centerY += point.y;
  13.     }
  14.     centerX = centerX / 4;
  15.     centerY = centerY / 4;
  16.     /* 如果位于中心点右上,则index = 0
  17.      * 如果位于中心点左上,则index = 1
  18.      * 如果位于中心点左下,则index = 2
  19.      * 如果位于中心点右下,则index = 3
  20.      */
  21.     Point[] result = new Point[4];
  22.     for (Point point : corners) {
  23.         if (point.x < centerX && point.y < centerY) {
  24.             result[3= point;
  25.         } else if (point.x > centerX && point.y < centerY) {
  26.             result[2= point;
  27.         } else if (point.x > centerX && point.y > centerY) {
  28.             result[1= point;
  29.         } else if (point.x < centerX && point.y > centerY) {
  30.             result[0= point;
  31.         }
  32.     }
  33.     corners.clear();
  34.     for (Point point : result) {
  35.         corners.add(0, point);
  36.     }
  37. }

本期到此为止。《基于AI的计算机视觉识别在Java项目中的使用》专题将按下列章节展开,欢迎关注我的个人公众号和CSDN。

一、《基于AI的计算机视觉识别在Java项目中的使用 —— 背景》

二、《基于AI的计算机视觉识别在Java项目中的使用 —— OpenCV的使用》

三、《基于AI的计算机视觉识别在Java项目中的使用 —— 搭建基于Docker的深度学习训练环境》

四、《基于AI的计算机视觉识别在Java项目中的使用 —— 准备深度学习训练数据》

五、《基于AI的计算机视觉识别在Java项目中的使用 —— 深度模型的训练调优》

六、《基于AI的计算机视觉识别在Java项目中的使用 —— 深度模型在Java环境中的部署》

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/108039
推荐阅读
相关标签
  

闽ICP备14008679号