用Java+OpenCV学习计算机视觉-空间滤波与边缘检测_java opencv 边缘算法增强-CSDN博客

用Java+OpenCV学习计算机视觉-空间滤波与边缘检测

空间滤波

空间滤波和后来的深度学习里面的卷积的概念是类似的,之前在实习面试字节跳动的时候,还没有学计算机视觉这门课程,但是有深度学习的一些基础,面试官问我知不知道空间滤波,我说不知道。。。现在才发现空间滤波和卷积的概念是类似的。。
关于空间滤波和卷积的概念这篇文章讲的挺不错的,可以去看看:https://www.cnblogs.com/xiaojianliu/p/9075872.html
其实就相当于一个小矩阵在一个大矩阵上进行从上到下,从左到右的滑动,然后运算是矩阵对应位置的点乘。
对应的代码实现如下:

    /**
     * 滤波,不会检测滤波器的有效性,忽略边缘。
     *
     * @param src    原图像
     * @param kernel 滤波器
     * @return 滤波之后的图像
     */
    public static Mat filter(Mat src, Mat kernel) {
        //忽略边缘
        Mat dst = new Mat(src.rows() - kernel.rows() + 1, src.cols() - kernel.cols() + 1, CvType.CV_8SC1);
        int rowBegin = kernel.rows() / 2;
        int colBegin = kernel.cols() / 2;
        //进行卷积操作
        for (int i = 0; i < src.rows() - 2 * rowBegin; i++) {
            for (int j = 0; j < src.cols() - 2 * colBegin; j++) {
                double result = 0.0;
                for (int ii = 0; ii < kernel.rows(); ii++) {
                    for (int jj = 0; jj < kernel.cols(); jj++) {
                        result += src.get(i + ii, j + jj)[0] * kernel.get(ii, jj)[0];
                    }
                }
                dst.put(i, j, result);
            }
        }
        return dst;
    }

Sobel滤波器实现边缘检测

本次我们用到的滤波器就是Sobel滤波器,如下:在这里插入图片描述
Sobel滤波器的作用是:如果把图像看做二维函数,那么sobel算子就是图像在水平和垂直方向变化的速度。在数学属于中,这种速度称为梯度。它是一个二维向量,向量的元素是横竖两个方向的函数的一阶导数。

从这个水平和垂直的变化速度我们可以做一件事:那就是检测图像的边缘轮廓(因为我们很容易知道在图像的边缘处图像的像素点的变化是比较大的)。
我们编写一个测试代码来将我们的原图像和水平方向上的Sobel滤波器进行滤波操作,代码如下:

public class SobelFilter {
    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        Mat src = imread("Data/house.jpg");
        Imgproc.resize(src, src, new Size(0.5 * src.width(), 0.5 * src.height()));
        CvUtils.imshow(src, "Origin");

        //X方向的Sobel滤波
        Mat x_kernel = new Mat(3, 3, CvType.CV_8SC1);
        x_kernel.put(0, 0,
                -1, 0, 1,
                -2, 0, 2,
                -1, 0, 1);
        Mat x_dst = CvUtils.filter(src, x_kernel);
        Mat x_show_dst = new Mat();
        x_dst.convertTo(x_show_dst, CvType.CV_8UC1, 1, 128);
        CvUtils.imshow(x_show_dst, "X Sobel");
    }
}

其中用到了我自己写的类似C++的OpenCV的imshow()函数,实现如下:

    /**
     * 类似C++ OpenCV内的imshow()函数
     * @param src 需要显示的Mat
     * @param namedWindow 窗口名称
     */
    public static void imshow(Mat src, String namedWindow) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new CvShowFrame(src, namedWindow);
            frame.setVisible(true);
        });
    }




public class CvShowFrame extends JFrame {
    BufferedImage _src;

    public CvShowFrame(Mat src, String namedWindow) throws HeadlessException {
        super();
        //转化为Java的BufferedImage
        //(使用的是HuiGui.toBufferedImage()函数的封装)
        this._src = Mat2BufImg(src);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setTitle(namedWindow);
        setSize(_src.getWidth(), _src.getHeight() + 30);
        repaint();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.drawImage(_src, 0, 30, null);
    }
}

最后的效果如下图所示:
在这里插入图片描述
可以看到我们图像的大致轮廓能够显示出来,同样的,我们对Y方向上进行Sobel滤波操作,代码如下:

    //Y方向的Sobel滤波
    Mat y_kernel = x_kernel.t();
    Mat y_dst = CvUtils.filter(src, y_kernel);
    Mat y_show_dst = new Mat();
    y_dst.convertTo(y_show_dst, CvType.CV_8UC1, 1, 128);
    CvUtils.imshow(y_show_dst, "Y Sobel");

可以使用Mat的t()方法来进行矩阵的转置(因为X方向和Y方向上的Sobel滤波器本身就是互为转置矩阵的)。

运行结果如下:
在这里插入图片描述
可以看到一个是X方向上面的细节更多一些,一个是Y方向上面的细节更多一些,所以我们可以将两者结合起来进行操作,实现图像的边缘检测。

这里使用的是L1范数来进行整个方向上的边缘的检测,简单来说就是对应的两个方向上面的矩阵的绝对值之和(L2范数就是欧式距离,平方开根号)。
在C++内的OpenCV可以很方便的使用Mat dst=abs(x_dst)+abs(y_dst);来进行计算,因为它重载了+操作符等,但是在Java内就没有那么方便了,所以我们得自己来进行计算,取出两个方向上面的所有的点,然后绝对值相加,放到新的Mat里面,最后显示出来就可以了。
具体代码实现如下:

        //整个方向的sobel滤波
        Mat sum_dst = new Mat(x_dst.rows(), x_dst.cols(), CvType.CV_8UC1);
        //取出X方向滤波后的图像的像素值
        byte[] x_dst_values = new byte[x_dst.rows() * x_dst.cols()];
        x_dst.get(0, 0, x_dst_values);
        //取出Y方向滤波后的图像的像素值
        byte[] y_dst_values = new byte[y_dst.rows() * y_dst.cols()];
        y_dst.get(0, 0, y_dst_values);
        //绝对值相加
        double[] sum_dst_values = new double[x_dst_values.length];
        for (int i = 0; i < x_dst_values.length; i++) {
            sum_dst_values[i] = Math.abs(x_dst_values[i]) + Math.abs(y_dst_values[i]);
        }
        //放置到新的Mat里面
        sum_dst.put(0, 0, sum_dst_values);
        //转化一下格式,方便显示出来
        sum_dst.convertTo(sum_dst, CvType.CV_8UC1, 1, 128);
        
        CvUtils.imshow(sum_dst, "sum_dst");

最后的运行结果如下图所示:
在这里插入图片描述
可以看到我们的原图像的边缘应该是很明显的能看出来了。

同样的,我们还可以对梯度幅值进行阈值化,可得到一个二值边缘分布图。这里主要使用的是threshold()函数,我们将大于200灰度值的像素点变成1,其他为0的二值图像,代码实现如下:

        Mat sum_dst_bin=new Mat();
        Imgproc.threshold(sum_dst,sum_dst_bin,200,255,Imgproc.THRESH_BINARY);
        CvUtils.imshow(sum_dst_bin,"sum_dst_bin");

最后的效果如下:
在这里插入图片描述
这个就比上面的图像的边缘显示的更加明显了。
最后来张全家福:
在这里插入图片描述
GitHub链接:https://github.com/MyLovePoppet/CV

上一篇灰度直方图及均衡化

https://blog.csdn.net/qq_41723475/article/details/105324200

下一篇


原网址: 访问
创建于: 2024-01-05 10:45:37
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论