Java 基于 OpenCV4.4 - 学习Canny函数处理图片教程 (六)

一、高亮显示图片中的物体

一张图片中包含一组物体、动物或者形状,也许是因为你想得到图像中物体的个数,想把它们高亮显示出来。在OpenCV中提供了一个非常有名的函数叫作Canny,它可以高亮显示图像中的线条。OpenCV的Canny函数可以检测灰度矩阵中的轮廓。我们需要做的只是把输入的矩阵转换为灰度图像,剩下的工作将由Canny完成。通过Core类中的cvtColor函数,OpenCV可以很容易地改变颜色空间。

1. 和往常一样加载如下图片

Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\333.jpg");

使用cvtColor函数来进行颜色转换,它的输入包含源矩阵、目标矩阵和目标颜色空间。颜色空间的常量可以在Imgproc类中找到,它们的名字以COLOR_为前缀。使用颜色常量COLOR_RGB2GRAY,可以把矩阵变成黑白两色。

 Imgproc.cvtColor(small, small,COLOR_RGB2GRAY);

接下来可以使用 Canny 函数 处理 由 cvtColor函数转换的黑白两色矩阵,代码如下:

         /**
             Canny函数包含以下参数:
             源矩阵
             目标矩阵
             低阈值,使用150.0
             高阈值,通常是低阈值的2倍或3倍
             光圈,3~7之间的一个奇数,我们使用3。光圈值越大,被检测到的轮廓越多
             L2梯度,暂时设置为true
         */
        Canny(small,small,150,300,3,true);

对每一个像素,Canny使用一个卷积矩阵包含一个核心像素和它的邻居像素,得到一个梯度值。如果梯度值大于高阈值,那么它就被检测为边界。如果梯度值在高阈值和低阈值之间,并且有个高阈值和它连接,那么它也会被保留。整个流程代码实现如下:

 @Test
    public void test16() throws Exception {
        URL url = ClassLoader.getSystemResource("lib/opencv_java440.dll");
        System.load(url.getPath());

        Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\225.jpg");
        Imgproc.cvtColor(small, small,COLOR_RGB2GRAY);
        /**
             Canny函数包含以下参数:
             源矩阵
             目标矩阵
             低阈值,使用150.0
             高阈值,通常是低阈值的2倍或3倍
             光圈,3~7之间的一个奇数,我们使用3。光圈值越大,被检测到的轮廓越多
             L2梯度,暂时设置为true
         */
        Canny(small,small,150,300,3,true);

        imwrite("D:\\ProgramFiles\\personDocument\\picture\\4.png", small);
        imshow("submat Image after:", small);
        waitKey();
    }

图片输出如图 所示:

为了保护眼睛、节省打印机油墨和树木资源,有些时候把矩阵中的白色变成黑色、黑色变成白色会让物体更容易辨认。反色操作可以通过Core类中的bitwise_not函数实现。代码如下:

// 克隆 Canny 处理过的 Mat
Mat reversal = small.clone();
// 反色操作
Core.bitwise_not(reversal,reversal);

整体代码实现流程如下:

@Test
    public void test17() throws Exception {
        URL url = ClassLoader.getSystemResource("lib/opencv_java440.dll");
        System.load(url.getPath());

        Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\225.jpg");
        Imgproc.cvtColor(small, small,COLOR_RGB2GRAY);
        /**
             Canny函数包含以下参数:
             源矩阵
             目标矩阵
             低阈值,使用150.0
             高阈值,通常是低阈值的2倍或3倍
             光圈,3~7之间的一个奇数,我们使用3。光圈值越大,被检测到的轮廓越多
             L2梯度,暂时设置为true
         */
        Canny(small,small,150,300,3,true);
        // 克隆 Canny 处理过的 Mat
        Mat reversal = small.clone();
        // 反色操作
        Core.bitwise_not(reversal,reversal);
        imwrite("D:\\ProgramFiles\\personDocument\\picture\\5.jpg", reversal);
        imshow("submat Image after:", reversal);
        waitKey();
    }
通过 Core.bitwise_not 反色 处理后的图片如下:

 二、使用Canny结果作为掩膜

Canny的边缘检测非常棒,它的输出还可以被作为掩膜(mask),用于生成一个精美的艺术化图片。当进行复制操作时,可以使用一个叫作掩膜的参数。掩膜是一个单通道的矩阵,值只包含0和1。当使用掩膜进行复制时,如果掩膜中的像素值是0的话,源矩阵中的像素就不会被复制,如果值是1的话,源像素就会被复制到目标矩阵中。

在上面中,我们根据bitwise_not函数输出的结果,得到了一个新的矩阵对象。其实现过程如下:

        Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\222.jpg");
        Imgproc.cvtColor(small, small,COLOR_RGB2GRAY);
        /**
             Canny函数包含以下参数:
             源矩阵
             目标矩阵
             低阈值,使用150.0
             高阈值,通常是低阈值的2倍或3倍
             光圈,3~7之间的一个奇数,我们使用3。光圈值越大,被检测到的轮廓越多
             L2梯度,暂时设置为true
         */
        Canny(small,small,150,300,3,true);
        // 反色操作
        Core.bitwise_not(small,small);

现在使用掩膜,我们来创建一个叫作target的白色矩阵,作为copy函数的目标参数。

Mat target = new Mat(small.height(),small.width(),CvType.CV_8UC3,new Scalar(255,255,255));

然后为copy函数加载一个源矩阵,我们需要确定它的大小和copy函数的目标矩阵(也就是target矩阵)的大小一致。让我们来调整背景对象的大小。

        Mat bg = imread("D:\\ProgramFiles\\personDocument\\picture\\333.jpg");
        Imgproc.resize(bg,bg,target.size());

 进行复制操作,代码实现:

        bg.copyTo(target,small);

        imwrite("D:\\ProgramFiles\\personDocument\\picture\\229.jpg", target);

完整代码实现如下:

@Test
    public void test18() throws Exception {
        URL url = ClassLoader.getSystemResource("lib/opencv_java440.dll");
        System.load(url.getPath());

        Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\222.jpg");
        Imgproc.cvtColor(small, small,COLOR_RGB2GRAY);
        /**
             Canny函数包含以下参数:
             源矩阵
             目标矩阵
             低阈值,使用150.0
             高阈值,通常是低阈值的2倍或3倍
             光圈,3~7之间的一个奇数,我们使用3。光圈值越大,被检测到的轮廓越多
             L2梯度,暂时设置为true
         */
        Canny(small,small,150,300,3,true);
        // 反色操作
        Core.bitwise_not(small,small);
        System.out.println(small);

        Mat target = new Mat(small.height(),small.width(),CvType.CV_8UC3,new Scalar(255,255,255));

        Mat bg = imread("D:\\ProgramFiles\\personDocument\\picture\\333.jpg");
        Imgproc.resize(bg,bg,target.size());
        bg.copyTo(target,small);

        imwrite("D:\\ProgramFiles\\personDocument\\picture\\229.jpg", target);
        imshow("submat Image after:", target);
        waitKey();
    }

 其中代码中引用的 222.jpg 和 333.jpg 分别如下:

 输出的图片如下:

为什么 222.jpg 是白色的? 
      正确答案其实是,底层的矩阵在初始化时是纯白色的,参照new Mat(..., WHITE)声明。当掩膜阻碍了一个像素的复制,也就是说掩膜中这个像素对应的值是0时,矩阵原来的颜色就会显示出来,这里是白色,这也是图1-28中的猫咪是白色的原因。你当然可以尝试一个黑色背景的源矩阵,或者是自己选择一个图片。

三、使用轮廓进行边缘检测

OpenCV中有两个函数常与Canny函数一同使用:findContours和drawContours。

findContours读入一个矩阵,并在这个矩阵中查找边缘,或者说定义形状的边界。因为原图像可能包含许多颜色和亮度的噪声,你通常需要一个经过预处理的图片,即一个由Canny处理过的黑白矩阵。

drawContours读入findContours的结果,也就是一组轮廓对象,并允许你用具体的特征来绘制这些轮廓,例如绘制线条的粗细和颜色。

OpenCV的findContours函数输入一个预处理过的图片,包含以下参数:

1.预处理过的矩阵

2.用于接收轮廓对象的空队列(MatOfPoint)

3.一个分层矩阵,你目前可以忽略它,并把它设置为空矩阵

4.轮廓追踪模式,例如是否建立轮廓之间的关系或返回所有内容

5.存储轮廓的近似类型,例如是绘制所有的点还是只绘制一些关键点

第一步,我们把预处理图片和追踪轮廓一起放在自定义的find_contours函数中。代码实现:

    static List findContours(Mat image,boolean onBlank){
        Mat mat = new Mat();
        Imgproc.cvtColor(image, mat,COLOR_RGB2GRAY);
        Canny(mat,mat,150,300,3,true);

        List contours = new ArrayList<MatOfPoint>();
        Imgproc.findContours(mat,contours,new Mat(),Imgproc.RETR_LIST,Imgproc.CHAIN_APPROX_SIMPLE);
        return contours;
    }

该函数返回一组检测到的轮廓,每个轮廓包含一组像素点,用OpenCV的话说,就是一个MatOfPoint对象。

接下来,我们定义一个draw_contours函数,读入源矩阵来找出第一步中得到的每个轮廓的大小,输入还包括我们希望用来绘制边缘的线条粗度。

在OpenCV中绘制轮廓,通常需要一个for循环,并把要绘制的轮廓索引给drawContours函数。代码实现:

    static Mat drawContours(Mat originalMat,List contours,int thickness){
        Mat mat = new Mat(originalMat.height(),originalMat.width(),CvType.CV_8UC3,new Scalar(255,255,255));
        for (int i = 0 ; i<contours.size();i++)
            Imgproc.drawContours(mat,contours,i,new Scalar(0,0,0),thickness);
        return mat;
    }

 使用 中的方法 ,调用上边封装的两个静态方法,实现轮廓检测,完整代码实现:

    @Test
    public void test19() throws Exception {
        URL url = ClassLoader.getSystemResource("lib/opencv_java440.dll");
        System.load(url.getPath());

        Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\222.jpg");

        List contours = findContours(small,true);
        Mat target = drawContours(small,contours,7);

        imwrite("D:\\ProgramFiles\\personDocument\\picture\\223.jpg", target);
        imshow("submat Image after:", target);
        waitKey();
    }

效果如图所示 

接下来,我们可以使用结果矩阵作为掩膜进行背景复制。下面的代码取自 标题二中的例子。该函数读入一个掩膜,并且用这个掩膜进行复制。

    static Mat maskOnBg(Mat mask,String bgFilePath){
        Mat target = new Mat(mask.height(),mask.width(),CvType.CV_8UC3,new Scalar(255,255,255));
        // "D:\\ProgramFiles\\personDocument\\picture\\333.jpg"
        Mat bg = imread(bgFilePath);
        Imgproc.resize(bg,bg,target.size());
        bg.copyTo(target,mask);
        return target;
    }

 完整代码实现如下:

    @Test
    public void test20() throws Exception {
        URL url = ClassLoader.getSystemResource("lib/opencv_java440.dll");
        System.load(url.getPath());

        Mat small = Imgcodecs.imread("D:\\ProgramFiles\\personDocument\\picture\\222.jpg");

        List contours = findContours(small,true);
        Mat target = drawContours(small,contours,7);

        Mat mask = maskOnBg(target,"D:\\\\ProgramFiles\\\\personDocument\\\\picture\\\\333.jpg");

        imwrite("D:\\ProgramFiles\\personDocument\\picture\\224.jpg", mask);
        imshow("submat Image after:", mask);
        waitKey();
    }

效果图如下:

 

如有不当之处请多多指教,如对你有所帮助,请评论或点赞予以支持,谢谢! 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用JavaOpenCV4.0.1-1.4.4实现图片中物品数量识别功能的示例代码: ```java import org.opencv.core.*; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; public class ItemCounter { public static void main(String[] args) { // 加载OpenCV库 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 读取图片 Mat image = Imgcodecs.imread("item.jpg"); // 转换为灰度图像 Mat gray = new Mat(); Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY); // 二值化图像 Mat binary = new Mat(); Imgproc.threshold(gray, binary, 100, 255, Imgproc.THRESH_BINARY); // 查找轮廓 Mat contours = new Mat(); Imgproc.findContours(binary, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); // 绘制轮廓 Mat result = new Mat(image.size(), CvType.CV_8UC3, new Scalar(0, 0, 0)); Imgproc.drawContours(result, contours, -1, new Scalar(0, 255, 0), 2); // 统计物体数量 int count = contours.toArray().length; System.out.println("物品数量:" + count); // 显示结果 Imgcodecs.imwrite("result.jpg", result); } } ``` 在这个示例代码中,我们首先加载OpenCV库,然后读取一张名为“item.jpg”的图片。接着,我们将图片转换为灰度图像,并对其进行二值化处理,以便于查找轮廓。然后,我们使用OpenCV的findContours函数查找图像中的所有轮廓,并使用drawContours函数绘制这些轮廓。最后,我们统计轮廓的数量,即为物品的数量,并将结果输出到控制台上。同时,我们将绘制轮廓后的结果保存为“result.jpg”文件,并显示出来。 需要注意的是,本示例代码中的图片路径需要根据实际情况进行修改。同时,我们在统计轮廓数量时使用了contours.toArray().length方法,其中contours是一个MatOfPoint类型的对象,toArray()方法将其转换为一个Point类型的数组,length方法返回该数组的长度,即为轮廓的数量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值