Ⅰ. 边缘检测算法
0x01.Canny边缘检测
Canny边缘检测算法是由4步构成,分别介绍如下:
第一步:噪声去除
由于边缘检测很容易受到噪声的影响,所以首先使用高斯滤波器去除噪声,在图像平滑那一章节中已经介绍过。
第二步:计算图像梯度
对平滑后的图像使用 Sobel 算子计算水平方向和竖直方向的一阶导数( 和 )。根据得到的这两幅梯度图( 和 )找到边界的梯度和方向,公式如下:
如果某个像素点是边缘,则其梯度方向总是垂直与边缘垂直。
梯度方向被归为四类:垂直,水平,和两个对角线方向。
第三步:非极大值抑制
在获得梯度的方向和大小之后,对整幅图像进行扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。如下图所示:
A点位于图像的边缘,在其梯度变化方向,选择像素点B和C,用来检验A点的梯度是否为极大值,若为极大值,则进行保留,否则A点被抑制,最终的结果是具有“细边”的二进制图像。
第四步:滞后阈值
现在要确定真正的边界。 我们设置两个阈值: 和 。
当图像的灰度梯度高于时被认为是真的边界, 低于 的边界会被抛弃。如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃。如下图:
如上图所示,A 高于阈值 所以是真正的边界点,C 虽然低于但高于 并且与 A 相连,所以也被认为是真正的边界点。而 B 就会被抛弃,因为低于 而且不与真正的边界点相连。所以选择合适的 和对于能否得到好的结果非常重要。(指定的越大,标准越高,指定的越小,标准越低)
💬API
canny = cv2.Canny(image, threshold1, threshold2)
💎示例:
img = cv2.imread('sun.jpg')
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
v1 = cv2.Canny(img, 80, 150)
v2 = cv2.Canny(img, 50, 100)
res = np.hstack((v1,v2))
cv2.imshow('res', res)
cv2.waitKey(0) # 等待时间,毫秒级,0表示按任意键终止
cv2.destroyAllWindows()
Ⅱ. 图像轮廓
0x00 轮廓检测
💬API
cv2.findContours(img,mode,method)
mode:轮廓检索模式
- RETR_EXTERNAL:只检索最外面的轮廓;-
- RETR_LIST:检索所有的轮廓,并将其保存到-条链表当中;
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:层是各部分的外部边界,第二层是空洞的边界;
- RETR_TREE:检索所有的轮廓,瓶构嵌套轮廓的整个层次;(最常用,可检测所有)
method:轮廓逼近方法
- CHAIN_ APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
-
CHAIN APPROX_SIMPLE:压缩水平的、 殖的和斜的部分,也就是,函数只保留他们的终点部分。
Ⅲ. 直方图
0x00 灰度直方图
原理
直方图是对数据进行统计的一种方法,并且将统计值组织到一系列实现定义好的 bin 当中。
其中, bin 为直方图中经常用到的一个概念,可以译为 “直条” 或 “组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、方向、色彩或任何其他特征。
图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素个数。
这种直方图中,横坐标的左侧为较暗的区域,而右侧为较亮的区域。因此一张较暗图片的直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。
📌 注意:直方图是根据灰度图进行绘制的,而不是彩色图像。
假设有一张图像的信息(灰度值 0 - 255,已知数字的范围包含 256 个值,于是可以按一定规律将这个范围分割成子区域(也就是 bins)。如:
然后再统计每一个 bin(i) 的像素数目。可以得到下图(其中 x 轴表示 bin,y 轴表示各个 bin 中的像素个数):
直方图的一些术语和细节:
- dims:需要统计的特征数目。在上例中,dims = 1 ,因为仅仅统计了灰度值。
- bins:每个特征空间子区段的数目,可译为 “直条” 或 “组距”,在上例中, bins = 16。
- range:要统计特征的取值范围。在上例中,range = [0, 255]。
直方图的意义:
- 直方图是图像中像素强度分布的图形表达方式。
- 它统计了每一个强度值所具有的像素个数。
- 不同的图像的直方图可能是相同的
直方图的计算和绘制
我们使用OpenCV中的方法统计直方图,并使用matplotlib将其绘制出来。
💬API
cv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]])
参数:
- images: 原图像。当传入函数时应该用中括号 [] 括起来,例如:[img]。
- channels: 如果输入图像是灰度图,它的值就是 [0];如果是彩色图像的话,传入的参数可以是 [0],[1],[2] 它们分别对应着通道 B,G,R。
- mask: 掩模图像。要统计整幅图像的直方图就把它设为 None。但是如果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并使用它。(后边有例子)
- histSize: BIN 的数目。也应该用中括号括起来,例如:[256]。
- ranges: 像素值范围,通常为 [0,256]
💎示例:
绘制灰度直方图
# 1 直接以灰度图的方式读入
img = cv2.imread('sun.jpg',0)#0表示灰度图
# 2 统计灰度图
hist = cv2.calcHist([img], [0], None, [256], [0,256])
# 3 绘制灰度图
plt.figure(figsize=(10,6),dpi=100)
plt.plot(hist)
plt.grid()
plt.show()
绘制彩色直方图
img = cv2.imread('sun.jpg')
color=('b', 'g', 'r')
for i,col in enumerate(color):
histr =cv2.calcHist([img],[i], None,[256],[0,256])
plt.plot(histr,color=col)
plt.xlim([0,256])
plt.show()
0x01 掩膜
掩膜是用选定的图像、图形或物体,对要处理的图像进行遮挡,来控制图像 处理的区域。
在数字图像处理中,我们通常使用二维矩阵数组进行掩膜。掩膜是由0和1组成一个二进制图像,利用该掩膜图像要处理的图像进行掩膜,其中1值的区域被处理,0 值区域被屏蔽,不会处理。
掩膜的主要用途是:
- 提取感兴趣区域:用预先制作的感兴趣区掩模与待处理图像进行”与“操作,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0。
- 屏蔽作用:用掩模对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计。
- 结构特征提取:用相似性变量或图像匹配方法检测和提取图像中与掩模相似的结构特征。
- 特殊形状图像制作
掩膜在遥感影像处理中使用较多,当提取道路或者河流,或者房屋时,通过一个掩膜矩阵来对图像进行像素过滤,然后将我们需要的地物或者标志突出显示出来。
我们使用cv.calcHist()来查找完整图像的直方图。
如果要查找图像某些区域的直方图,该怎么办?
只需在要查找直方图的区域上创建一个白色的掩膜图像,否则创建黑色, 然后将其作为掩码mask传递即可。
💎示例:
# 1. 直接以灰度图的方式读入
img = cv2.imread('sun.jpg', 0)
# 2. 创建蒙版
mask = np.zeros(img.shape[:2], np.uint8)
mask[400:650, 200:500] = 255
# 3.掩模
masked_img = cv2.bitwise_and(img,img,mask = mask)
# 4. 统计掩膜后图像的灰度图
mask_histr = cv2.calcHist([img],[0],mask,[256],[1,256])
# 5. 图像展示
fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(img,cmap=plt.cm.gray)
axes[0,0].set_title("Original image")
axes[0,1].imshow(mask,cmap=plt.cm.gray)
axes[0,1].set_title("Masking data")
axes[1,0].imshow(masked_img,cmap=plt.cm.gray)
axes[1,0].set_title("Post-mask data")
axes[1,1].plot(mask_histr)
axes[1,1].grid()
axes[1,1].set_title("Grayscale histogram")
plt.show()
Ⅳ. 直方图均衡化
0x01 原理与应用
想象一下,如果一副图像中的大多数像素点的像素值都集中在某一个小的灰度值值范围之内会怎样呢?
如果一幅图像整体很亮,那所有的像素值的取值个数应该都会很高。所以应该把它的直方图做一个横向拉伸(如下图),就可以扩大图像像素值的分布范围,提高图像的对比度,这就是直方图均衡化要做的事情。
“直方图均衡化”是把原始图像的灰度直方图从比较集中的某个灰度区间变成在更广泛灰度范围内的分布。
直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。
这种方法提高图像整体的对比度,特别是有用数据的像素值分布比较接近时,在X光图像中使用广泛,可以提高骨架结构的显示,另外在曝光过度或不足的图像中可以更好的突出细节。
使用opencv进行直方图统计时,使用的是:
dst = cv.equalizeHist(img)
返回:
- dst : 均衡化后的结果
💎 示例:
img = cv2.imread('sun.jpg', 0)
dst = cv2.equalizeHist(img)
plt.hist(dst.ravel(),256)
plt.show()
0x01 自适应的直方图均衡化
上述的直方图均衡,我们考虑的是图像的全局对比度。 在进行完直方图均衡化之后,图片背景的对比度被改变了,但是我们丢失了很多信息,所以在许多情况下,这样做的效果并不好。如下图所示,对比下两幅图像中雕像的画面,由于太亮我们丢失了很多信息。
为了解决这个问题, 需要使用自适应的直方图均衡化。
此时, 整幅图像会被分成很多小块,这些小块被称为“tiles”(在 OpenCV 中 tiles 的 大小默认是 8x8),然后再对每一个小块分别进行直方图均衡化。 所以在每一个的区域中, 直方图会集中在某一个小的区域中)。
但如果有噪声的话,噪声会被放大。为了避免这种情况的出现要使用对比度限制。对于每个小块来说,如果直方图中的 bin 超过对比度的上限的话,就把 其中的像素点均匀分散到其他 bins 中,然后在进行直方图均衡化。
最后,为了 去除每一个小块之间的边界,再使用双线性差值,对每一小块进行拼接。
💬API
cv.createCLAHE(clipLimit, tileGridSize)
参数:
- clipLimit: 对比度限制,默认是40
- tileGridSize: 分块的大小,默认为8*8
💎示例:
img = cv2.imread('sun.jpg', 0)
dst = cv2.equalizeHist(img)
plt.hist(dst.ravel(),256)
clahe = cv2.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
res_clahe = clahe.apply(img)
res = np.hstack((img, dst, res_clahe))
cv2.imshow('res',res)
总结
1. 灰度直方图:
- 直方图是图像中像素强度分布的图形表达方式。
- 它统计了每一个强度值所具有的像素个数。
- 不同的图像的直方图可能是相同的
cv.calcHist(images,channels,mask,histSize,ranges [,hist [,accumulate]])
2. 掩膜
创建蒙版,透过mask进行传递,可获取感兴趣区域的直方图
3. 直方图均衡化:增强图像对比度的一种方法
cv.equalizeHist( ): 输入是灰度图像,输出是直方图均衡图像
4. 自适应的直方图均衡
将整幅图像分成很多小块,然后再对每一个小块分别进行直方图均衡化,最后进行拼接
clahe = cv.createCLAHE(clipLimit, tileGridSize)