OpenCV-Python笔记(上)

安装

全局安装

pip install opencv-python

项目虚拟环境安装

# 进入项目根路径执行
.venv/bin/pip install opencv-python

计算机眼中的图像

一张图片由大小比如(100*100)决定,说明存在100*100的像素点,每个像素点存在颜色通道,我们所看到的彩色均由RGB(Red红色、、Green绿色、Blue蓝色)三原色组成,不同的颜色组合在一起就会在视觉上看到新的颜色,比如红色+绿色,看到的就是黄色

在这里插入图片描述

因为RGB三通道模式,对于彩色图片就存在 2 3 2^3 23即八种标准颜色(只考虑0或者255)。

  • 纯红色:RGB(255, 0, 0)
  • 纯绿色:RGB(0, 255, 0)
  • 纯蓝色:RGB(0, 0, 255)
  • 黄色(红色+绿色):RGB(255, 255, 0)
  • 青色(绿色+蓝色):RGB(0, 255, 255)
  • 品红色(红色+蓝色):RGB(255, 0, 255)
  • 白色:RGB(255, 255, 255)
  • 黑色:RGB(0, 0, 0)

在计算机中我们用[r,g,b]这样一个定长的一维数组表示一个像素点,其中元素顺序可变,所以存在RGB,BGR模式的说法。于是对于一张图片的表示如下:

图 片 矩 阵 [ [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] ⋯ [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] [ r , g , b ] ⋯ [ r , g , b ] [ 255 , 0 , 0 ] ⋮ ⋮ ⋮ ⋮ [ 255 , 0 , 0 ] [ r , g , b ] ⋯ [ r , g , b ] [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] ⋯ [ 255 , 0 , 0 ] [ 255 , 0 , 0 ] ] 图片矩阵 \left[ \begin{matrix} [255,0,0] & [255,0,0] & \cdots & [255,0,0] & [255,0,0]\\ [255,0,0] & [r,g,b] & \cdots & [r,g,b] & [255,0,0]\\ \vdots & \vdots & & \vdots& \vdots && \\ [255,0,0] & [r,g,b] & \cdots & [r,g,b] & [255,0,0]\\ [255,0,0] & [255,0,0] & \cdots & [255,0,0] & [255,0,0] \end{matrix} \right] [255,0,0][255,0,0][255,0,0][255,0,0][255,0,0][r,g,b][r,g,b][255,0,0][255,0,0][r,g,b][r,g,b][255,0,0][255,0,0][255,0,0][255,0,0][255,0,0]

我们用[r,g,b]表示一个像素点,于是图片矩阵中每一行的像素点表示为:

red_row1 = [[r1,g1,b1],...,[rn,gn,bn]]

上面这样的行存在多少个呢?这就是列,也就是图片高度height的像素点个数。

red_img = [[[r1,g1,b1],...,[rn,gn,bn]]
,
		  [[r1,g1,b1],...,[rn,gn,bn]]
,...,
		  [[r1,g1,b1],...,[rn,gn,bn]]
]

最后图片在计算机看到的数据就是这样:
在这里插入图片描述

如果我们只查看几个像素点数据,可以这样:

img = cv2.imread("img.png")  
# 获取高度两个像素点,宽度3个像素点,即共6个像素点展示  
print(img[:2, :3:])  
print("="*20)  
# 获取高度一个像素点,宽度三个像素点,B通道的数据  
b = img[:1, :3, 0]  
# 获取高度一个像素点,宽度三个像素点,G通道的数据  
g = img[:1, :3, 1]  
# 获取高度一个像素点,宽度三个像素点,R通道的数据  
r = img[:1, :3, 2]  
print(b)  
print(g)  
print(r)

输出
在这里插入图片描述

总结: 计算机中的图片由矩阵像素点(pixel) 表示,其中每一个像素点由一维数组[b,g,r]定长一维数组表示,不同的数值代表不同的颜色,按照三原色通道,彩色图存在三个通道的二维数组

Note:

  • 通道顺序是可变的,不同的排列意味着模式不同[b,g,r]表示BGR模式[r,b,g]表示RBG模式。
  • 对于灰度图,因为不需要三个通道表示,因此一个数值就表示一个像素点,所以灰度图单纯是一个二维矩阵描述图片像素点。
  • OpenCV读取图片默认是BGR模式

ROI

ROI(Region Of Interest) 感兴趣的区域,可用于截取特定区域图或者特定通道图。

截取特定区域

# 读取图片为三维数组数据
img = cv2.imread("person.jpg")  
# 截取高度500像素,宽度1000像素
img = img[0:500, 0:1000]  

# 查看图片,按q退出
cv2.imshow('person', img)  
if cv2.waitKey() & 0xFF == 'q':  
    cv2.destroyAllWindows()

效果图
在这里插入图片描述

颜色通道提取

img = cv2.imread("person.jpg")  
# 返回不同通道的数据,是一个二维数组!!!  
b, g, r = cv2.split(img)  
  
# 只保留b通道数据的图片  
blue_img = img.copy()  
blue_img[:, :, 1] = 0  
blue_img[:, :, 2] = 0  
  
# 只保留g通道数据的图片  
green_img = img.copy()  
green_img[:, :, 0] = 0  
green_img[:, :, 2] = 0  
  
# 只保留r通道数据的图片  
red_img = img.copy()  
red_img[:, :, 0] = 0  
red_img[:, :, 1] = 0

效果图
在这里插入图片描述

图片融合

# cv2打开图片默认为BGR格式,需要转为RGB格式  
target = cv2.cvtColor(cv2.imread(img1), cv2.COLOR_BGR2RGB)  
height, width, _ = target.shape  
cv2_img2 = cv2.cvtColor(cv2.imread(img2), cv2.COLOR_BGR2RGB)  
  
# 两张图片融合需要保证宽高一致,因此需要重新调整大小  
background = cv2.resize(cv2_img2, (width, height))  
  
# 合并图片  
merged = cv2.addWeighted(target, 0.6, background, 0.4, 0)

# 展示图片需要RGB再次转为BGR(如果单纯展示图片前面加载图片就不用转了),如果需要图片保存则需要保存为RGB格式  
cur_img = cv2.cvtColor(merged,cv2.COLOR_RGB2BGR)  
cv2.imshow("merged", cur_img)  
  
# 按q退出,或者指定waiKey()指定时长(毫秒)后自动退出,为0则不退出,按键q退出  
if cv2.waitKey(0) & 0xFF == ord('q'):  
    cv2.destroyAllWindows()

效果图

在这里插入图片描述

边界填充

其实就是指定上下左右四个方向应该填充什么颜色。

img = cv2.imread("person.jpg")  
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

# 填充的区域大小:上下左右
top, bottom, left, right = (50, 50, 50, 50)  

# 不同的填充策略
# 1. 复制法,就是直接把边缘的像素复制
replicate = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REPLICATE)  
# 2. 反射法,比如 321|12345|543
reflect_101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT_101)  
# 3. 反射法101,比如 432|12345|4321
reflect101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT101)  
# 4. 包装法,比如 345|12345|123
wrap = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_WRAP)  
# 5. 常量值填充,通过value指定常量值
constant = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT,value=255)  
  
images = [img, replicate, reflect_101, reflect101, wrap, constant]  
titles = ["ORIGINAL", "REPLICATE", "REFLECT_101", "REFLECT101", "WRAP", "CONSTANT"]  

# matplot绘图查看结果
rows, cols = 2, 3  
for i in range(rows * cols):  
    plt.subplot(int(f"{rows}{cols}{i+1}")), plt.imshow(images[i], 'gray'), plt.title(titles[i])  
    plt.axis('off')  
    plt.xticks([])  
    plt.yticks([])  
  
plt.show()

效果图

在这里插入图片描述

数值计算

img = cv2.imread('person.jpg')  
# 每个像素点的数值都+30,如果超过255则%256,比如刚好256则会变成黑色0  
img2 = img + 30  
  
# 直接相加,如果超过则固定数值255  
img3 = cv2.add(img, img)

图像阈值

ret, dst = cv2.threshold(src, thres, maxval, type)
  • ret:这个返回值是实际使用的阈值。如果type参数中使用了cv2.THRESH_OTSUcv2.THRESH_TRIANGLE,则ret是自动计算得到的最佳阈值,而thresh参数在这种情况下被忽略。如果不使用这些自动阈值计算方法,ret将与thresh参数的值相同。
  • dst:输出图
  • src:输入图
  • thresh:阈值数值
  • maxval:超出阈值后应该设置的值,或者小于,根据type策略决定
  • type:策略类型
    • cv2.THRESH_BINARY:如果像素值大于阈值,则像素值被设置为maxval;否则,像素值被设置为0。
    • cv2.THRESH_BINARY_INV:这是cv2.THRESH_BINARY的反向操作。
    • cv2.THRESH_TRUNC:如果像素值大于阈值,则像素值被设置为阈值;否则,像素值保持不变。
    • cv2.THRESH_TOZERO:如果像素值大于阈值,则像素值保持不变;否则,像素值被设置为0。
    • cv2.THRESH_TOZERO_INV:上面的反向操作
    • cv2.THRESH_OTSU:自动选择最佳阈值的方法。
    • cv2.THRESH_TRIANGLE:OpenCV 4.x中引入的一个选项,它使用了一种基于图像直方图的三角形方法来寻找最佳全局阈值。
img = cv2.imread('person.jpg')  
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

# 不同阈值设置得到的结果图
ret, dst1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)  
ret, dst2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)  
ret, dst3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)  
ret, dst4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)  
ret, dst5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)  
  
images = [img, dst1, dst2, dst3, dst4, dst5]  
titles = ["ORIGINAL", "BINARY", "BINARY_INV", "TRUNC", "TOZERO", "TOZERO_INV"]  
explains = ["原图", "大于则为阈值,否则为0", "小于则为阈值,否则为0", "大于阈值则设置为阈值", "小于阈值则设置为0",  
            "大于阈值则设置为0"]  
  
rows = 2  
cols = 3  

# plt绘制图
for i in range(rows * cols):  
    plt.subplot(int(f"{rows}{cols}{i + 1}")), plt.imshow(images[i]), plt.title(titles[i])  
    # 在图片下方添加注释  
    plt.text(x=0.5, y=-0.1, s=explains[i], fontsize=10,  
             ha='center', va='top', transform=plt.gca().transAxes)  
    plt.axis('off')  
    plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

图像处理

1. 均值滤波

将像素点周围的数加起来计算平均值,我们设置的大小则为滤波核,或者叫卷积核,单词为kernel,在参数里边为ksize

在这里插入图片描述

这么计算的结果更像是把所有的像素点按照卷积核大小进行了一个平滑处理,当卷积核越大,影响的面积就越大,响应的平均后的值越均匀,给人的视觉效果就是更模糊

# 卷积3x3对应下图中间
ret1 = cv2.blur(noisy, (3, 3))
# 卷积4x4对应下图左上
ret1 = cv2.blur(noisy, (3, 3))

# 第一叫均值滤波,第二个叫方框滤波,结果完全是一样的
ret1 = cv2.blur(noisy, (3, 3))  
# 参数-1输出值像素深度保持一致,深度值得的是表示像素的位数,比如8位,16位,32位
ret2 = cv2.boxFilter(noisy, -1, (3, 3), normalize=True)

效果图
在这里插入图片描述

2. 高斯滤波

高斯滤波区别均值滤波的核心点在于权重概念,离像素点越远的位置权重比越小。

在这里插入图片描述

高斯滤波的核心是正态分布,然后计算权重得出的,高斯滤波处理的图片会比均值滤波更清晰因为距离越远受响应程度越小。

img = cv2.imread('person_noisy.png')  
  
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)  
  
ret1 = cv2.blur(img, (25, 25))  
# 最后一个参数为sigma=0,标准差设置0让cv2自己计算合理的标准差  
ret2 = cv2.GaussianBlur(img, (25, 25), 0)  
  
plt.figure(figsize=(12,4))  
plt.subplot(131), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(132), plt.imshow(ret1), plt.title("均值滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(133), plt.imshow(ret2), plt.title("高斯滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.tight_layout()  
plt.show()

效果图

在这里插入图片描述

3. 中值滤波

中值滤波计算方式最简单,将滤波核里边的数据取出来排序后取中间值,作为代替的值。

在这里插入图片描述

对于上面这种特殊例子,255永远是最大值,因此直接被过滤掉了。

img = cv2.imread('person_noisy.png')  
  
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  
  
ret1 = cv2.blur(img, (25, 25))  
# 最后一个参数为sigma=0,标准差设置0让cv2自己计算合理的标准差  
ret2 = cv2.GaussianBlur(img, (25, 25), 0)  
# 参数比较特殊,实际25等价上面的元组(25, 25)  
ret3 = cv2.medianBlur(img, 25)  
  
plt.subplot(221), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(222), plt.imshow(ret1), plt.title("均值滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(223), plt.imshow(ret2), plt.title("高斯滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(224), plt.imshow(ret3), plt.title("中值滤波(25,25)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.tight_layout()  
plt.show()

效果图
在这里插入图片描述

形态学

1. 腐蚀操作

所谓腐蚀操作,是对图像处理效果的描述,原理则是根据给定的卷积核结构元(structuring element)遍历图像,然后取最小值赋值给目标像素点,最后得到一个处理后的结果矩阵。

计算原理遵循两个点:

  1. 卷积核完全处在图像内,确定的中心点就是目标点(是否需要腐蚀)
  2. 如果在这个卷积核区域内,只要存在最小值通常为二值图,也即是黑色0,则腐蚀中心点(也就是赋值最小值。)

示意图如下

其中图中框选区域就是卷积核结构元,就是一个3*3的矩阵,可见,只要白色区域不能完全覆盖卷积核,则中心点被设置为背景色黑色0。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
kernel = np.ones((90, 90), dtype=np.uint8)  
  
ret = cv2.erode(img, kernel, iterations=1)  
# 错误的使用,这实际上是利用卷积结构为一维数组,数据元素为5进行卷积运算,也就是说,1*2两个像素点大小的卷积核!!!  
# ret = cv2.erode(img, (5,5), iterations=9)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("腐蚀前(原图)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("腐蚀后)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()  

效果图
在这里插入图片描述

Note: 这里有个需要特别指明的点,在使用上,卷积核是一个矩阵结构,只不过里边的数都是1,如果错误使用,比如我这样👇

ret = cv2.erode(img, (5,5), iterations=9)

实际上是指定卷积核为1*2像素的一个长方形,里边元素是5,也就是对每个1*2大小的像素块的值乘5,导致结果并不会判断最小值0,也就是下面的结果,腐蚀效果看不到,实际上就腐蚀了大小为2的很小一个像素点。

错误的结果图:
在这里插入图片描述

总结:所谓腐蚀操作,**就是利用卷积核(也叫结构元)进行移动,只要所到之处没有被白色完全包裹,就会腐蚀中心点,**因此对于上面规整的黑白图,卷积核为10,实际就是白色区域减少10个白色像素点,如果设置20则减少20个白色像素点。

另外,所谓迭代次数,就是在操作后的基础上再次移动的意思。如果卷积核设置足够大,可以一次腐蚀操作直接全部腐蚀完。

在这里插入图片描述

如果卷积核大小完全覆盖白色区域,且在图片内,则一次腐蚀完毕!

2. 膨胀操作

如果理解了腐蚀操作,那么膨胀操作就很好理解了,完全是一个相反的操作,腐蚀操作是赋值卷积核内最小的数值,膨胀操作则恰恰相反,赋值最大的值也就是视觉上的白色。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
# 生成卷积核矩阵
kernel = np.ones((110, 110), dtype=np.uint8)

# 膨胀操作  
ret = cv2.dilate(img, kernel, iterations=1)  

# 输出结果图
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)

plt.subplot(121), plt.imshow(img), plt.title("膨胀前(原图)"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("膨胀后"), plt.axis('off'), plt.xticks([]), plt.yticks([])

plt.show()

效果图
在这里插入图片描述

3. 开运算和闭运算

3.1 开运算

开运算(先腐蚀,再膨胀)。

在这里插入图片描述

这么看可能会认为这是无效的操作,看下面的图👇

在这里插入图片描述

可以看到,如果原图的白色很细小,不影响整体的腐蚀,那么开运算就可以达到去除杂质的效果。

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((90, 90), dtype=np.uint8)  
  
# 开运算  
ret = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("开运算"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

3.2 闭运算

闭运算(先膨胀,再腐蚀)。

在这里插入图片描述

闭运算并不能做到类似开运算的结果,相反他会让原来的图形轮廓变大(不同的图闭运算效果也不同)

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((90,90), dtype=np.uint8)  
  
# 闭运算  
ret = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("闭运算"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

4. 梯度运算

梯度运算(膨胀-腐蚀)。

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((5,5), dtype=np.uint8)  
  
# 梯度运算  
ret = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("梯度运算运算"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

5. 礼帽和黑帽

5.1 礼帽

礼帽(原图-开运算结果)。

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((95, 95), dtype=np.uint8)  
  
# 礼帽运算(原图-开运算结果)  
ret = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("礼帽"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

5.2 黑帽

黑帽(闭运算结果-原图)。

在这里插入图片描述

img = cv2.imread('open.png', cv2.IMREAD_GRAYSCALE)  
kernel = np.ones((95, 95), dtype=np.uint8)  
  
# 黑帽运算(闭运算结果-原图)  
ret = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("黑帽"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图

在这里插入图片描述

图形梯度

注意: 这里的梯度,指得是图像强度或颜色的方向变化。 他是用来衡量变化幅度的,比如黑色到白色,直接变化255,就是很大的一个梯度,可以理解为楼梯的陡峭程度;而形态学中的梯度是一种运算操作。

一个像素点左边跟右边的差值就是梯度,这个就是x轴方向的梯度,上边跟下边的差值,就是y轴方向的梯度。而得出这个差异值的计算方法,就是算子,OpenCV提供了三种算子:SobelScharr和Laplacian,高大上的名词就叫梯度滤波器高通滤波器

这个本质跟形态操作是一样的,只是设置的卷积矩阵值不同。

在这里插入图片描述

我们通过设置卷积矩阵里边元素的数值,做到右边-左边差异值的结果作为当前像素的梯度表示。

中 心 点 梯 度 = [ 0 0 255 0 0 255 0 0 255 ] ∗ 卷 积 矩 阵 [ − 1 0 1 − 2 0 2 − 1 0 1 ] = 255 ∗ 1 + 255 ∗ 2 + 255 ∗ 1 = 255 中心点梯度= \begin{bmatrix} 0&0&255\\ 0&0&255\\ 0&0&255 \end{bmatrix}* 卷积矩阵 \begin{bmatrix} -1&0&1\\ -2&0&2\\ -1&0&1\\ \end{bmatrix} = 255*1 + 255 * 2 + 255*1 = 255 =000000255255255121000121=2551+2552+2551=255

动态效果图

1. Sobel算子

当我们设置卷积矩阵为下面的时候,就是Sobel算子。

S o b e l [ − 1 0 1 − 2 0 2 − 1 0 1 ] Sobel \begin{bmatrix} -1&0&1\\ -2&0&2\\ -1&0&1 \end{bmatrix} Sobel121000121

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Sobel算子,这里深度指定为-1,x轴计算  
ret = cv2.Sobel(img, -1, 1, 0)   

# 转换通道,仅用于展示用
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("Sobel算子结果"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
  
plt.show()

效果图
在这里插入图片描述

这里存在两个问题:

  1. 没有计算y轴的梯度
  2. 右边的梯度被忽略了

关于右边的梯度问题,这是因为我们设置的深度为-1,什么是深度? 我们一直默认颜色数值表示是0~255就是因为默认是uint8数据类型为无符号的8位,因此当黑色-白色为负数时,被截断为0,同理当计算超过255时则被截断为255。

但这显然不是我们期望的结果,因为我们想要整张图的梯度而无关正数和负数,因此我们需要将深度改为更多位数的表示,比如32F,64F,即带符号的32位表示,64位表示。同时对梯度结果取绝对值,排除负数。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Sobel算子,这里深度指定深度为无符号64位,x轴计算  
ret = cv2.Sobel(img, cv2.CV_64F, 1, 0)  
# 将梯度结果取绝对值  
ret = cv2.convertScaleAbs(ret)  
  
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  
ret = cv2.cvtColor(ret, cv2.COLOR_GRAY2RGB)  
  
plt.subplot(121), plt.imshow(img), plt.title("原图"), plt.axis('off'), plt.xticks([]), plt.yticks([])  
plt.subplot(122), plt.imshow(ret), plt.title("Sobel算子结果"), plt.axis('off'), plt.xticks([]), plt.yticks([])

效果图

在这里插入图片描述

对y轴的梯度计算同理,我们直接看最后的使用

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Sobel算子,这里深度指定深度为无符号64位,x轴计算  
x_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 0)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
# Sobel算子,这里深度指定深度为无符号64位,x轴计算  
y_gradient = cv2.Sobel(img, cv2.CV_64F, 0, 1)  
y_gradient = cv2.convertScaleAbs(y_gradient)  
# Sobel算子,这里深度指定深度为无符号64位,x,y轴同时计算  
xy_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 1)  
xy_gradient = cv2.convertScaleAbs(xy_gradient)  
  
# x轴和y轴的融合结果(x轴权重0.5,y轴权重0.5),标量值设置为0 ==> dst=src1⋅α+src2⋅β+γ  
x_add_y_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)

# 图片展示代码略

效果图

在这里插入图片描述

可见,同时对xy轴计算梯度的效果并不好,因此最好分别计算x轴和y轴最后加权平均。

2. Scharr算子

有了前面的基础,这里就很简单了,当我们设置卷积矩阵为下面的时候,就是Scharr算子。

S c h a r r [ − 3 0 3 − 10 0 10 − 3 0 3 ] Scharr \begin{bmatrix} -3&0&3\\ -10&0&10\\ -3&0&3 \end{bmatrix} Scharr31030003103

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)  
  
# Scharr算子,这里深度指定深度为无符号64位,x轴计算  
x_gradient = cv2.Scharr(img, cv2.CV_64F, 1, 0)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
# Sobel算子,这里深度指定深度为无符号64位,x轴计算  
y_gradient = cv2.Scharr(img, cv2.CV_64F, 0, 1)  
y_gradient = cv2.convertScaleAbs(y_gradient)

# 如果Sobel指定ksize=-1就是等价Scharr的用法
# cv2.Sobel(src,cv2.CV_64F,1,0,ksize=-1)

# x轴和y轴的融合结果(x轴权重0.5,y轴权重0.5),标量值设置为0 ==> dst=src1⋅α+src2⋅β+γ
x_add_y_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)

3. Laplacian算子

当卷积核为下面的数值时,则为Laplacian算子。

L a p l a c i a n [ 0 1 0 1 − 4 1 0 1 0 ] Laplacian \begin{bmatrix} 0&1&0\\ 1&-4&1\\ 0&1&0 \end{bmatrix} Laplacian010141010

4. 三种算子的比较


img = cv2.imread('person.jpg', cv2.IMREAD_GRAYSCALE)  

# Sobel算子
x_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 0)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
y_gradient = cv2.Sobel(img, cv2.CV_64F, 0, 1)  
y_gradient = cv2.convertScaleAbs(y_gradient)  
sobel_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)  
  
# Scharr算子, Sobel函数指定ksize=-1就是Scharr  
x_gradient = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=-1)  
x_gradient = cv2.convertScaleAbs(x_gradient)  
y_gradient = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=-1)  
y_gradient = cv2.convertScaleAbs(y_gradient)  
scharr_gradient = cv2.addWeighted(x_gradient, 0.5, y_gradient, 0.5, 0)  

# Laplacian算子
laplacian_gradient = cv2.Laplacian(img, cv2.CV_64F)  
laplacian_gradient = cv2.convertScaleAbs(laplacian_gradient)

# 图片展示代码略

效果图
在这里插入图片描述

总结: Sobel算子主要用于边缘检测,Scharr算子同理但是他更能捕捉细节,二者对噪音点的抵抗都还可以,Laplacian算子噪音点对识别的影响较大,但是能够提供更清晰的边缘效果。


参考链接

[1]:B站视频&opencv从入门到实战
[2]:官网Docs4.10.0
[3]:中文文档
[4]:原来卷积是这么计算的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/876004.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ppt文件怎么压缩变小一些?8种压缩PPT文件的方法推荐

ppt文件怎么压缩变小一些?在现代工作环境中,PPT文件常常是我们展示信息和分享想法的主要工具。然而,当这些文件变得庞大时,它们不仅会占用大量的存储空间,还可能导致处理速度变慢,影响整体工作效率。这种情…

4+1视图模型

逻辑视图(Logical View) 逻辑视图主要关注系统的功能分解,即系统如何被划分为不同的逻辑组件(如类、接口、包等),以及这些组件之间的交互关系。它帮助开发者理解系统的业务逻辑和功能结构。 开发视图&…

【人工智能】OpenAI发布GPT-o1模型:推理能力的革命性突破,这将再次刷新编程领域的格局!

在人工智能领域,推理能力的提升一直是研究者们追求的目标。就在两天前,OpenAI正式发布了其首款具有推理能力的大语言模型——o1。这款模型的推出,不仅标志着AI技术的又一次飞跃,也为开发者和用户提供了全新的工具来解决复杂问题。…

【实践】应用访问Redis突然超时怎么处理?

目录标题 问题描述分析过程查看监控数据系统监控指标JVM监控指标Redis监控指标分析应用异常单机异常规律集群异常规律统计超时的key 初步结论验证结论访问Redis链路slowlogRedis单节点info all定位redis节点定位异常keybigkeystcpdump定位大key影响 经验总结 问题描述 某产品线…

【验收交付资料】系统培训方案(doc原件)

1. 培训目的 2. 培训方式 3. 培训内容 4. 培训讲师 5. 培训教材 6. 培训质量保证 软件全套资料部分文档清单: 工作安排任务书,可行性分析报告,立项申请审批表,产品需求规格说明书,需求调研计划,用户需求调查…

Pikachu靶场之XSS

先来点鸡汤,少就是多,慢就是快。 环境搭建 攻击机kali 192.168.146.140 靶机win7 192.168.146.161 下载zip,pikachu - GitCode 把下载好的pikachu-master,拖进win7,用phpstudy打开网站根目录,.....再用…

豆包MarsCode编程助手:产品功能解析与应用场景探索!

随着现代技术的不断进化升级,人工智能正在逐步改变着我们的日常工作方式。特别是对于复杂的项目,代码编写、优化、调试、测试等环节充满挑战。为了简化这些环节、提高开发效率,许多智能编程工具应运而生,豆包MarsCode 编程助手就是…

nodejs基础教程之-异步编程promise/async/generator

1. 异步 所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段,比如,有一个任务是读取文件进行处理,异步的执行过程就是下面…

二、Kubernetes中pod的管理及优化

一 kubernetes 中的资源 1.1 资源管理介绍 在kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes。 kubernetes的本质上就是一个集群系统,用户可以在集群中部署各种服务 所谓的部署服务,其实就是在kub…

【运维监控】Prometheus+grafana监控zookeeper运行情况

运维监控系列文章入口:【运维监控】系列文章汇总索引 文章目录 一、prometheus二、grafana三、prometheus集成grafana监控zookeeper1、修改zookeeper配置2、修改prometheus配置3、导入grafana模板4、验证 本示例通过zookeeper自带的监控信息暴露出来,然后…

Ceisum(SuperMap iClient3D for Cesium)实现平面裁剪

1:参考API文档:SuperMap iClient3D for Cesium 开发指南 2:官网示例:support.supermap.com.cn:8090/webgl/Cesium/examples/webgl/examples.html#layer 3:SuperMap iServer:欢迎使用 SuperMap iServer 11…

C语言---函数指针基础总结万字(4)

一、 函数 1.函数是一段可以重复执行的代码。 它可以接受不同的参数, 完成对应的操作。 下面的例子就是一个函数 int plus(int n) {return n; }上面的代码声明了一个函数plus()。 2.函数声明的语法有以下几点,需要注意。 返回值类型。 函数声明时&a…

每日奇难怪题(持续更新)

1.以下程序输出结果是() int main() {int a 1, b 2, c 2, t;while (a < b < c) {t a;a b;b t;c--;}printf("%d %d %d", a, b, c); } 解析:a1 b2 c2 a<b 成立 ,等于一个真值1 1<2 执行循环体 t被赋值为1 a被赋值2 b赋值1 c-- c变成1 a<b 不成立…

【油猴脚本】00006 案例 Tampermonkey油猴脚本自定义表格列名称,自定义表格表头,自定义表格的thead里的td

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【油…

数据结构一:绪论

&#xff08;一&#xff09;数据结构的基本概念 1.相关名词 【1】数据 1.信息的载体&#xff0c;描述客观事物 2.能被输入到计算机中 3.能被计算机程序识别和处理的符号的集合。 【2】数据元素 1.数据的一个“个体” 2.数据的基本单位 3.有时候也被称为元素、结点、顶点…

【STM32】外部中断

当程序正常运行执行main函数&#xff0c;此时如果外部中断来了&#xff0c;执行外部中断函数&#xff0c;实现相应的功能&#xff0c;然后就可以回到main. 一般stm32芯片每个引脚都有自己的外部中断&#xff0c;但是为了限制&#xff0c;会有一个中断线&#xff0c;对应一个中断…

前端Excel热成像数据展示及插值算法

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏:《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 目录 &#x1f4d8; 前言 &#x1f4d8;一、热成像数…

服务器数据增量迁移方案-—SAAS本地化及未来之窗行业应用跨平台架构

一、数据迁移增量同步具有以下几个优点&#xff1a; 1. 减少数据传输量&#xff1a;只传输自上次同步以来更改的数据&#xff0c;而不是整个数据集&#xff0c;这显著降低了网络带宽的使用和传输时间。 2. 提高同步效率&#xff1a;由于处理的数据量较小&#xff0c;同步过程…

MyBatis中Collection和Association的底层实现原理

MyBatis中Collection和Association的底层实现原理 Hi &#x1f44b;, Im shy 有人见尘埃&#xff0c;有人见星辰 技术咨询 引言 在 MyBatis 中&#xff0c;<collection> 和 <association> 标签用于处理一对多和一对一的关系。这两个标签在底层通过缓存、对象创…

以系统工程为指导的军品设计、开发与管理常用方法培训

课程背景&#xff1a; 产品开发和产品管理是组织经营战略的核心&#xff0c;而经营战略又为组织的创新战略、产品开发和产品管理提供了环境和方向。使命、愿景与核心价值观对于产品开发的聚焦点和管理方式都具有十分重要的作用。产品开发通常被称为组织的“血液”&#xff0c;…