Java使用OpenCV轮廓标记图中物品并分别截取保存

直接上代码块了,导入opencv的步骤就不再赘述了,很多java opencv教程都只写了怎么导入,其他啥都没写,随便搜都是一大把

//连接导入并使用opencv包
System.setProperty("java.awt.headless", "false");
System.out.println(System.getProperty("java.library.path"));
URL url = ClassLoader.getSystemResource("lib/opencv/opencv_java4100.dll");
System.load(url.getPath());

     // 导入原始图片
Mat img = Imgcodecs.imread(这里输入你的图片路径,最好使用绝对路径,例:D:\XXXX\XXX.jpg);
     // 调整原始图片大小,看个人选择调整合适即可,改中间的0.3,0.3
Imgproc.resize(img, img, new Size(), 0.3, 0.3, Imgproc.INTER_LINEAR);
     // 做原始图片的备份
Mat imgOri = img.clone();
     // 展示原始图片
HighGui.imshow("original", imgOri);


     // 创建一个新 Mat对象,准备用于灰度处理
Mat imgGray = new Mat();
     // 灰度处理 第一个输入想要处理的图片,第二个输入灰度处理对象
Imgproc.cvtColor(img, imgGray, Imgproc.COLOR_BGR2GRAY);
     // 展示灰度处理图片
HighGui.imshow("gray", imgGray);

 

     // 创建一个新 Mat对象,准备用于高斯模糊
Mat imgBlur = new Mat();
     // 高斯模糊 第一个输入原始对象,也就是做过灰度处理的图片,第二个是用于存储高斯模糊的对象
Imgproc.GaussianBlur(imgGray, imgBlur, new Size(3, 3), 0, 0);
     // 展示高斯模糊图片
HighGui.imshow("blur", imgBlur);

 

     // 经过灰度处理和高斯模糊之后,边缘检测的效果就会非常好了,所以这里先创建一个新 Mat对象,准备进行坎迪边缘检测
Mat imgCanny = new Mat();
     // 坎迪边缘检测 第一个输入高斯模糊处理后的图片 第二个输入存储坎迪边缘检测对象,0和50是边缘检测的阈值,需要根据实际情况调整,用于判断像素是否保留
     // 这两个阈值推荐的比例在 2 :1 或 3 :1 之间; 3是sobel算子的孔径大小,默认值就是3 ; false是计算图像梯度幅值的标识,默认值就是false
Imgproc.Canny(imgBlur, imgCanny, 0, 50, 3, false);
     // 展示边缘检测
HighGui.imshow("canny", imgCanny);

 

     // 腐蚀膨胀操作的核,表示参考点位于中心的3X3的核
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
     // 边缘检测之后可以看到有很多线条和点,这个时候就需要使用膨胀来弱化消除这些多余部分,所以这里创建新 Mat对象用于存储膨胀后的图片
Mat imgDilate = new Mat();
     // 膨胀 第一个参数是坎迪边缘检测的对象 第二个参数是存储膨胀的对象 第三个是参考点
     // 第四个 Point点是锚的位置,默认值为(-1,-1),表示位于图片的中心,最后的12是迭代的次数,这个次数根据实际情况调整,默认为1
Imgproc.dilate(imgCanny, imgDilate, element, new Point(-1, -1), 12);
     // 展示膨胀后的图片
HighGui.imshow("dilate", imgDilate);

 

     // 膨胀后的小线条和点基本已经被全部消除了,但这时图片过于臃肿,所以需要创建一个新的 Mat对象,用于腐蚀瘦身
Mat imgErode = new Mat();
     // 腐蚀 第一个参数是膨胀后的 Mat对象 第二个参数是腐蚀对象,后面参数意义与之前膨胀大同,不再赘述
Imgproc.erode(imgDilate, imgErode, element, new Point(-1, -1), 10);
     // 展示腐蚀图片
HighGui.imshow("erode", imgErode);

 

     // 此时图片的边缘已经基本定型,下面需要解决白圈中间类似孔洞的问题,这里使用的是漫水填充,首先创建一个新的 Mat填充对象,直接克隆的腐蚀图
Mat imgFlood = imgErode.clone();
     // 填充需要一个大于操作对象图行列各2的 Mat掩码对象,所以给这个 mask创建出来
Mat mask = Mat.zeros(imgErode.rows() + 2, imgErode.cols() + 2, CvType.CV_8UC1);
     // 这是漫水填充的颜色,选择与包裹孔洞的白圈相同的白色
Scalar scalar = new Scalar(255, 255, 255);
     // 漫水填充还需要一个开始的种子点位,默认是(0,0)的图片边缘点
Point seed = new Point(0, 0);
     // 其实漫水填充方法还有其他参数,这里都是默认值,就不填写了,第一个参数是填充对象,第二个参数是mask对象,第三个参数是起始种子,第四个是填充颜色
Imgproc.floodFill(imgFlood, mask, seed, scalar);
     // 展示漫水填充后的图片
HighGui.imshow("flood", imgFlood);

 

     // 漫水填充后,可能有的老哥不懂,为啥要这样提取,也用不起来呀,这其实是为了做取反,然后与腐蚀图做交集
     // 这样就可以得到没有孔洞的边缘图,所以先创建一个新的取反 Mat对象,直接克隆填充图
Mat imgFloodBitWise = imgFlood.clone();
     // 使用 opencv Core中的取反方法,第一个参数是取反前的对象,第二个参数是取反后的对象
Core.bitwise_not(imgFlood, imgFloodBitWise);
     // 展示取反的图片
HighGui.imshow("bitwise_not", imgFloodBitWise);

 

     // 现在应该看图都明白了吧,孔洞变成了白色,这样与之前带孔洞的腐蚀图刚好对应,直接求一个交集,来得到没有孔洞的腐蚀图吧
     // 创建一个新的 Mat对象,用于存放交集图
Mat imgUnion = new Mat();
     // 使用 opencv Core中的交集方法,第一个、第二个参数是用来求交集的 Mat图,第三个参数是存储交集图的对象
Core.bitwise_or(imgErode, imgFloodBitWise, imgUnion);
     // 展示交集图
HighGui.imshow("union", imgUnion);

 

     // 这时的图片,边界大小已经很明显了对吧,这里再用一次坎迪边缘检测来得到更加准确的边缘图吧
Mat imgCanny1 = new Mat();
     // 与之前坎迪边缘检测同理
Imgproc.Canny(imgUnion, imgCanny1, 0, 50, 3, false);
     // 展示新的坎迪边缘检测图
HighGui.imshow("canny1", imgCanny1);

 


     // 现在只剩最后两步了,就是在原始图中画出想要截取的范围,然后看看你想怎么处理
     // 这里先创建一个点位列表来存储需要操作的对象点
List<MatOfPoint> contours = new ArrayList<>();
     // 创建一个新的 Mat对象来存储标记后的图片
Mat hierarchy = new Mat(imgCanny.size(), CvType.CV_32S);
     // 寻找点位并存储进对象
Imgproc.findContours(imgCanny1, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);

     // 我们这里是准备截取出来图中的物品图麻,所以需要创建一个空 Mat对象来反复使用
Mat imgRoi = null;
     // 其实 for循环直接用标准的 (int i = 1; i < contours.size()+1 ; i++) 就可以省去定义这个 i的步骤了
int i = 1;
     // 遍历列表,操作对象
for (MatOfPoint contour : contours) {
       // 创建一个矩形操作对象,用于框出图片中识别的物品
Rect rectC = Imgproc.boundingRect(contour);
       // 根据点位进行轮廓标记,画出矩形图片,颜色选择的是(0,0,255):红色
Imgproc.rectangle(img, new Point(rectC.x, rectC.y),
new Point(rectC.x + rectC.width, rectC.y + rectC.height),
new Scalar(0, 0, 255), 1, Imgproc.LINE_AA);
       // 做去重操作,将物品图提取出来
if (i % 2 == 1) {
          // 创建一个新的 Mat对象,用于存储截取的图片
imgRoi = new Mat(imgOri, rectC);
Imgcodecs.imwrite(这里填写储存图片的地址和图片名称,建议写绝对路径,例:D:\XXXX\" + i + ".jpg", imgRoi);
          // 展示一下存储的图片吧,程序里面直接保存在地址上了,没有用imshow

         

            }
i++;
}

     // 展示轮廓标记
HighGui.imshow("result", img);

 

     // 经典等待任意按键,不按不结束
HighGui.waitKey();

// 释放全部 Mat资源
img.release();
imgOri.release();
imgGray.release();
imgBlur.release();
imgCanny.release();
element.release();
imgErode.release();
imgFlood.release();
mask.release();
imgFloodBitWise.release();
imgUnion.release();
imgCanny1.release();
hierarchy.release();
imgRoi.release();
// 关闭全部窗口
HighGui.destroyAllWindows();
System.exit(0);




最后释放资源的时候你应该也发现了,创建了太多太多Mat对象了,实际上应该可以精简一些,这些就交给使用它们的你们来进一步优化吧
参考的话,只能说感谢互联网,因为参考的网站页面实在是太多了,数不过来,主要是这个 opencv关于 java的使用手册和教程内容实在是少,就不贴参考链接了,实在是太多了整不过来
posted @ 2024-06-25 16:03  Lee597  阅读(358)  评论(0)    收藏  举报