OpenCV笔记021————Image Convolution(卷积看这里)

图像卷积操作Image Convolution的代码注释:

import cv2 as cv
import numpy as np


def custom_blur(src):
    h, w, ch = src.shape
    print("h , w, ch", h, w, ch)
    result = np.copy(src)
    for row in range(1, h-1, 1):
        for col in range(1, w-1, 1):
            v1 = np.int32(src[row-1, col-1])
            v2 = np.int32(src[row-1, col])
            v3 = np.int32(src[row-1, col+1])
            v4 = np.int32(src[row, col-1])
            v5 = np.int32(src[row, col])
            v6 = np.int32(src[row, col+1])
            v7 = np.int32(src[row+1, col-1])
            v8 = np.int32(src[row+1, col])
            v9 = np.int32(src[row+1, col+1])

            b = v1[0] + v2[0] + v3[0] + v4[0] + v5[0] + v6[0] + v7[0] + v8[0] + v9[0];
            g = v1[1] + v2[1] + v3[1] + v4[1] + v5[1] + v6[1] + v7[1] + v8[1] + v9[1];
            r = v1[2] + v2[2] + v3[2] + v4[2] + v5[2] + v6[2] + v7[2] + v8[2] + v9[2];
            result[row, col] = [b//9, g//9, r//9]
    cv.imshow("result", result)


src = cv.imread("dataset/train/bees/1.png")
cv.namedWindow("input", cv.WINDOW_AUTOSIZE)
cv.imshow("input", src)
dst = cv.blur(src, (15, 15))
cv.imshow("blur", dst)
custom_blur(src)
cv.waitKey(0)
cv.destroyAllWindows()

运行结果:
在这里插入图片描述

在这里插入图片描述

图像卷积总结

一、认识卷积

数字图像是一个二维的离散信号,对数字图像做卷积操作其实就是利用卷积核(卷积模板)在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完所有图像的过程。
在这里插入图片描述
这个图可以清晰的表征出整个卷积过程中一次相乘后相加的结果;该图片选用3*3的卷积核,卷积核内共有9个数值,所以图片右上角公式中一共有9行,每一行都是图像像素值与卷积核上数值相乘,最终结果代替代替原图像中对应位置的值。这样沿着图片一步长为1滑动,每一个滑动后都一次相乘在相加的工作,可以得到最终输出结果。
请添加图片描述

除此之外,卷积核的选择有一些规则:

  • 卷积核大小一般为奇数,这样的话它会按照中间的像素点中心对称,所以卷积核一般都是3×3,5×5,7×7。有中心了,也有了半径的称呼,例如5*5大小的核的半径就是2。
  • 卷积核所有的元素之和一般都等于1.这是为了原始图像的能量(亮度)守恒。也存在卷积核元素相加不等于1的情况。若滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮;反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
  • 对于滤波后的结构,可能出现负数或者大于255的数值。对于这种情况,我们将它们直接截断到0和255之间即可。对于负数,也可以取绝对值。

二、边界补充问题

在这里插入图片描述

上面的图片说明了图像的卷积操作,但是他也反映出一个问题,如上图,原始图片尺寸为77,卷积核的大小为33,当卷积核沿着图片滑动后只能滑动出一个55的图片出来,这就造成了卷积后的图片和卷积前的图片尺寸不一致,这显然不是我们想要的结果,所以为了避免这种情况,需要先对原始图片做边界填充处理。在上面的情况中,我们需要先把原始图像填充为99的尺寸。
常用的区域填充方法包括:
为了画图方便,这里就不用55的尺寸了,用33定义原始图像的尺寸,补充为9*9的尺寸,图片上的颜色只为方便观看,并没有任何其他含义。
原始图像:
在这里插入图片描述
1. 补零
在这里插入图片描述

2.边界复制
在这里插入图片描述
3.镜像
在这里插入图片描述

4.块复制
在这里插入图片描述

三、不同卷积核下卷积意义

我们经常能看到的,平滑,模糊,去燥,锐化,边缘提取等等工作,其实都可以通过卷积操作来完成,下面我们一一举例说明一下:
1. 没有任何作用的卷积核
在这里插入图片描述
2.平滑均值滤波
该卷积核的作用在于取九个值的平均值代替中间像素值,所以起到的平滑的效果:
在这里插入图片描述
举例:
在这里插入图片描述在这里插入图片描述

3. 高斯平滑
高斯平滑水平和垂直方向呈现高斯分布,更突出了中心点在像素平滑后的权重,相比于均值滤波而言,有着更好的平滑效果。
在这里插入图片描述
举例
在这里插入图片描述
四、图像锐化
该卷积利用的其实是图像中的边缘信息有着比周围像素更高的对比度,而经过卷积之后进一步增强了这种对比度,从而使图像显得棱角分明、画面清晰,起到锐化图像的效果。
在这里插入图片描述
在这里插入图片描述

边缘锐化可以选择卷积核:
在这里插入图片描述

五、梯度Prewitt
水平梯度卷积核:
在这里插入图片描述
在这里插入图片描述

垂直梯度卷积核:
在这里插入图片描述
在这里插入图片描述
六、Soble边缘检测
梯度Prewitt卷积核与Soble卷积核的选定是类似的,都是对水平边缘或垂直边缘有比较好的检测效果。Soble与梯度Prewitt卷积核不同之处在于,Soble更强调了和边缘相邻的像素点对边缘的影响。
水平梯度:
在这里插入图片描述
在这里插入图片描述
垂直梯度:
在这里插入图片描述
在这里插入图片描述
七、梯度Laplacian
在这里插入图片描述
在这里插入图片描述
Laplacian也是一种锐化方法,同时也可以做边缘检测,而且边缘检测的应用中并不局限于水平方向或垂直方向,这是Laplacian与soble的区别。下面这张图可以很好的表征出二者的区别:来源于OpenCV官方文档。
在这里插入图片描述

四、OpenCV实现

API

void filter2D( InputArray src, 
               OutputArray dst, 
               int ddepth,
               InputArray kernel, 
               Point anchor = Point(-1,-1),
               double delta = 0, 
               int borderType = BORDER_DEFAULT 
               );

函数参数:

  • src:输入图像。
  • dst:输出与src具有相同大小和相同通道数的图像。
  • ddepth:目标图像的所需深度。
  • kernel:卷积核(或一个相关核),一个单通道浮点矩阵;如果要将不同的内核应用于不同的通道,请使用split将图像分为单独的色彩平面,并分别进行处理。
  • anchor:
    内核的锚点,指示内核中已过滤点的相对位置;锚点应位于内核内;默认值(-1,-1)表示锚位于内核中心。内核的基准点(anchor),其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。
  • delta: 在将像素存储到dst之前将其添加到过滤后的像素的可选值。 borderType:
  • 像素向外逼近的方法,默认值是BORDER_DEFAULT。

五、手写卷积操作

这个自己实现的卷积其实也依赖OpenCV,但是没有直接使用封装好的函数,这样更有利于了解图像卷积到底是如何完成的。这里面加了一个防溢出的函数,具体可以看聊一聊OpenCV的saturate_cast防溢出。

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>

using namespace  std;
using namespace  cv;

Mat Kernel_test_3_3 = (Mat_<double>(3,3) << 
    0,-1,0,
    -1,5,-1,
    0,-1,0);
void Convlution(Mat  InputImage,Mat  OutputImage,Mat kernel)
{
    //计算卷积核的半径
    int sub_x = kernel.cols/2;
    int sub_y = kernel.rows/2;
    //遍历图片  
    for (int image_y=0;image_y<InputImage.rows-2*sub_y;image_y++)
    {
        for(int image_x=0;image_x<InputImage.cols-2*sub_x;image_x++)
        {
            int pix_value = 0;
            for (int kernel_y = 0;kernel_y<kernel.rows;kernel_y++)
            {
                for(int kernel_x = 0;kernel_x<kernel.cols;kernel_x++)
                {
                    double  weihgt = kernel.at<double>(kernel_y,kernel_x)   ;
                    int value =  (int)InputImage.at<uchar>(image_y+kernel_y,image_x+kernel_x); 
                    pix_value +=weihgt*value;
                }
            }
            OutputImage.at<uchar>(image_y+sub_y,image_x+sub_x) = (uchar)pix_value;
            //OutputImage.at<uchar>(image_y+sub_y,image_x+sub_x) = saturate_cast<uchar>((int)pix_value);
            if ((int)pix_value!=(int)saturate_cast<uchar>((int)pix_value))
            {
                //cout<<"没有防溢出"<<(int)pix_value<<endl;
                //cout<<"防溢出"<<(int)saturate_cast<uchar>((int)pix_value)<<endl;
                //cout<<"没有防溢出写入了什么?"<<(int)OutputImage.at<uchar>(image_y+sub_y,image_x+sub_x)<<endl;
                //cout<<endl;
            }
        }
    }
}


int main()
{
    Mat srcImage = imread("1.jpg",0);
    namedWindow("srcImage", WINDOW_AUTOSIZE);
    imshow("原图", srcImage);

    //filter2D卷积
    Mat dstImage_oprncv(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));;
    filter2D(srcImage,dstImage_oprncv,srcImage.depth(),Kernel_test_3_3);
    imshow("filter2D卷积图",dstImage_oprncv);
    imwrite("1.jpg",dstImage_oprncv);

    //自定义卷积
    Mat dstImage_mycov(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));
    Convlution(srcImage,dstImage_mycov,Kernel_test_3_3);
    imshow("卷积图3",dstImage_mycov);
    imwrite("2.jpg",dstImage_mycov);

    waitKey(0);
    return 0;

}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值