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 中心点梯度= 000000255255255 卷积矩阵 121000121 =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} Sobel 121000121

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} Scharr 31030003103

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} Laplacian 010141010

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]:原来卷积是这么计算的

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Neo4j入门案例:西游记

创建一个基于《西游记》中“孙悟空”的黑神话版本的知识图谱。这个图谱将会包括《西游记》中的一些主要角色、地点、事件以及它们之间的关系。我们将创建至少10个节点和20个关系,并提供相应的Cypher语句。 数据模型定义 实体类型(节点) 角色…

Nuxt Kit 组件管理:注册与自动导入

title: Nuxt Kit 组件管理:注册与自动导入 date: 2024/9/15 updated: 2024/9/15 author: cmdragon excerpt: Nuxt Kit 为组件的注册和导入提供了灵活高效的解决方案。无论你是要批量导入组件,还是单独处理特定组件,这些工具都能够满足你的需求。使用这些方法可以显著提升…

路径规划——D*算法

路径规划——D*算法 D Star算法是一种用于动态环境下的算法,它可以在环境变化时快速更新路径。 算法原理 D Star算法是一种反向增量式搜索算法,反向即算法从目标点开始向起点逐步搜索;增量式搜索,即算法在搜索过程中会计算每一…

Navicat On-Prem Server 2.0 | MySQL与MariaDB基础管理功能正式上云

近日,Navicat 发布了 Navicat On-Prem Server 2.0 的重大版本更新。这标志着这款自2021年首发的私有云团队协作解决方案迈入了一个崭新的阶段。此次2.0版本的飞跃性升级,核心聚焦于MySQL与MariaDB基础管理功能的全面革新与强化,赋予了用户的操…

leaflet【十】实时增加轨迹点轨迹回放效果实现

实时轨迹回放 在前面有用leaflet-trackplayer实现了一个轨迹回放的效果,单击前往:轨迹回放效果&控制台控制轨迹运动效果 这篇文章主要是实现一下实时增加轨迹点,不改变原来运行轨迹和速度。这里是简易做了一个demo效果,大概…

计算机网络 --- 【1】欢迎来到计算机网络/计算机网络基本概念/计算机网络、互连网、互联网的区别

目录 一、计算机网络学什么? 二、 什么是计算机网络?计算机网络、互联网(因特网,Internet)、互连网(internet)之间的区别? 2.1 计算机网络的定义 2.2 计算机网络与互连网的区别 2.3 互连网 三、总结部分 一、计算机网络学…

Nginx+Tomcat(负载均衡、动静分离)

目录 一、Nginx概述 1.Nginx应用 二、正向代理和反向代理 1.正向代理 1.1主要作用 1.2工作原理 2.反向代理 2.1主要作用 2.2工作原理 三、负载均衡模式 1.轮询 2.最少连接数 3.IP 哈希 4.加权轮询 5.最少时间算法 6.一致性哈希 四、规划部署负载均衡和反向…

oracle数据库安装和配置详细讲解

​ 大家好,我是程序员小羊! 前言: Oracle 数据库是全球广泛使用的关系型数据库管理系统 (RDBMS),提供高性能、可靠性、安全性和可扩展性,广泛应用于企业关键任务系统。下面详细介绍如何在 CentOS 系统上安装和配置 Or…

【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树

在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析,整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…

智能家居环境监测系统设计(论文+源码)

1. 系统方案 系统由9个部分构成,分别是电源模块、烟雾传感器模块、GSM发送短信模块、报警模块、温度传感器模块、人体红外感应模块、按键设置模块、显示模块、MCU模块。各模块的作用如下:电源模块为系统提供电力;烟雾传感器模块检测烟雾浓度&…

[项目实战]EOS多节点部署

文章总览:YuanDaiMa2048博客文章总览 EOS多节点部署 (一)环境设计(二)节点配置(三)区块信息同步(四)启动节点并验证同步EOS单节点的环境如何配置 (一&#xf…

开放系统,面向各类业务需求可提供定制化服务的智慧物流开源了

智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。构建基于Ai技术的…

计算机毕业设计 智慧物业服务系统的设计与实现 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点…

Python数据分析 Pandas库-初步认识

Python数据分析 Pandas库-初步认识 认识Pandas ​ pandas是一个非常实用的Python工具,我们可以把它想象成一个超级强大的表格处理工具,它比Excel更智能,操作更为简单。pands可以从各种文件格式(CSV、JSON、SQL、Excel&#xff0…

ModbusTCP/RTU转Ethernet/IP(CIP)-Modbus设备与罗克韦尔AB的PLC之间通讯

IGT-DSER智能网关模块支持西门子、三菱、欧姆龙、罗克韦尔AB等各种品牌的PLC之间通讯,同时也支持PLC与Modbus协议的工业机器人、智能仪表、变频器等设备通讯。网关有多个网口、串口,也可选择WIFI无线通讯。无需PLC内编程开发,只要在IGT-DSER智…

AI大模型与产品经理:替代与合作的深度剖析

在创业的征途中,产品经理常常被外界以一种半开玩笑的口吻提及:“就差一个程序员了。”这句话背后,既蕴含着对产品经理创意与策略能力的认可,也揭示了技术实现环节对于产品成功不可或缺的重要性。然而,随着AI技术的飞速…

2024年微电子与纳米技术国际研讨会(ICMN 2024) Microelectronics and Nanotechnology

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网:https://ais.cn/u/vEbMBz提交检索:EI Compendex、IEEE Xplore、Scopus大会时间:2024年9月20-22日地点:成都…

Golang数据流处理:掌握Reader和Writer接口的技巧

Golang数据流处理:掌握Reader和Writer接口的技巧 引言理解Reader和Writer接口Reader接口的定义和基本方法Writer接口的定义和基本方法 Reader接口的深入探讨Reader接口的实现示例使用io.Reader读取文件内容从网络连接中读取数据 常用Reader类型及其应用场景strings.…

Selenium打开浏览器后闪退问题解决

笔者这两天在做一个自动化方案,用来优化数据统计。其中一部分数据需要通过云上堡垒机跳转访问,而这个堡垒机在笔者日常使用的火狐浏览器上运行不是很正常(表现在有些复制粘贴按钮显示不太灵敏)。 但在Edge浏览器上基本正常&#…

Unity3d 以鼠标位置点为中心缩放视角(正交模式下)

思路整理: 缩放前: 缩放后: 记录缩放前鼠标的屏幕坐标 A,计算鼠标位置对应的世界坐标 A_world 缩放完成后,根据当前屏幕下A所对应的世界坐标A1_world 计算A1_world 和 A_world 的偏移量 移动摄像机 代码&#xff…