这种设定全局阈值的来进行图像分割的方法还是比较好理解的, 即设定一个阈值, 跟图像的每一个像素点的灰度值比较, 然后根据上面的五种类型的对应规则改变图中像素点的灰度值来达到图像分割的目的.
ret, img = cv2.threshold (src, thresh, maxval, type)
参数 | 解释 |
---|---|
src | 源图片, 必须是单通道 |
thresh | 阈值, 取值范围 0 ~ 255 / 使用 OSTU时传入0值 |
maxal | 分配给像素值的最大值, 取值范围 0 ~ 255 |
type | 阈值类型, 即上图阈值类型的五种类型, 具体参数见下表 |
type | 解释 |
---|---|
cv2.THRESH_BINARY | 二进制阈值化 |
cv2.THRESH_BINARY_INV | 反二进制阈值化 |
cv2.THRESH_TRUNC | 截断阈值化 |
cv2.THRESH_TOZERO | 阈值化为0 |
cv2.THRESH_TOZERO_INV | 反阈值化为0 |
返回值 | 解释 |
---|---|
ret | 设定的阈值 / 或自动计算得到的阈值 |
img | 阈值化后的图像 |
import cv2
def main(filename:str)->None:
img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
print(img.dtype)
ret, b_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, b_img_inv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, img_trunc = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, img_tozero = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, img_tozero_inv = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
cv2.imshow("img", img)
cv2.imshow("THRESH_BINAR", b_img)
cv2.imshow("THRESH_BINAR_INV", b_img_inv)
cv2.imshow("THRESH_TRUC", img_trunc)
cv2.imshow("THRESH_TOZERO", img_tozero)
cv2.imshow("THRESH_TOZERO_INV", img_tozero_inv)
cv2.waitKey(0)
cv2.destroyAllWindows()
if(__name__ == "__main__"):
main("img/car.png")
在上面使用全局阈值时, 我们是手动自己设置了一个阈值, 那么如何确定这个阈值应该设置在那个范围合适, 一个做法即不停尝试不同的数值, 然后查看图片的处理结果, 而一个阈值是可以基于图像的灰度直方图来确定, 例如一副图像的灰度直方图是一副双峰图像, 一个好的阈值应该对应着两个峰之间的最小值, 而这里说的Otsu二值化就是选定阈值的其中一种方法.
opencv中使用OSTU还是用到 cv2.threshold()这个函数, 这不过在type类型这个参数需要多传入一个cv2.THRESH_OTSU, 同时还要把thresh阈值这个参数设置为0, 然后函数的返回值中的ret即根据这个算法找到的一个最优阈值。
ret, b_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
全局阈值仅在物体的灰度值和背景的灰度值不变的情况下效果很好, 但更多的情况是照明情况并不均匀, 故会造成使用全局阈值进行分割的结果不理想, 即使使用OSTU也不能有很好的效果(对于非双峰图像,这种方法得到的结果可能会不理想)。 即下面的第一幅和第二幅的处理结果都不是理想的情况
对于上面的问题是由于不均匀照明造成的, 因此不能找出一个对整幅图像都适用的固定阈值, 因为整幅图像在整体虽然光照不均匀, 但在局部区域中, 如上图所示文字是要比周围更暗的, 这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果,故这种将图像与局部背景进行比较的操作被称为动态阈值分割处理.
dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
参数 | 解释 |
---|---|
src | 源图片, 必须是单通道 |
maxValue | 分配给像素值的最大值, 取值范围 0 ~ 255 |
adaptiveMethod | 阈值的计算方法, 两种: |
1. cv2.ADAPTIVE_THRESH_MEAN_C 区域内均值 | |
2. cv2.ADAPTIVE_THRESH_GAUSSIAN_C 区域内像素点加权和,权重为一个高斯窗口 | |
thresholdType | 阈值类型, 就是前面说的五种, 不过这里只能用 cv2.THRESH_BINARY 和 cv2.THRESH_BINARY_INV这两种类型 |
blockSize | 规定领域大小(一个正方形的领域) |
C | 阈值等于均值或者加权值减去这个常数(为0相当于阈值 就是求得领域内均值或者加权值) |
返回值 | 解释 |
---|---|
dst | 阈值化后的图像 |
import cv2
def main(filename:str)->None:
img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
print(img.dtype)
adv_img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
cv2.imshow("img", adv_img)
cv2.waitKey(0)
cv2.imwrite("img/save3.bmp", adv_img)
cv2.destroyAllWindows()
if(__name__ == "__main__"):
main("img/test3.png")
可以看到效果相比于上面的全局阈值出来的效果好很多.
图像 g r , c g_{r, c} gr,c 代表理想物体, 即无缺陷物体的图像, 即作为参考图像. 图像 f r , c f_{r, c} fr,c 为待检测图像, 可以利用下面式子找出与参考图像相比偏差太大的像素点.
S = { ( r , c ) T ∈ R ∣ ∣ f r , c − g r , c ∣ > g a b s } S = \{(r, c) ^ T \in R \,\,\, |\,\,\,|f_{r, c} - g_{r, c}| > g_{abs} \} S={(r,c)T∈R∣∣fr,c−gr,c∣>gabs}
S = { ( r , c ) T ∈ R ∣ ∣ f r , c − g r , c ∣ > v r , c } S = \{(r, c) ^ T \in R \,\,\, |\,\,\,|f_{r, c} - g_{r, c}| > v_{r, c} \} S={(r,c)T∈R∣∣fr,c−gr,c∣>vr,c}
通过上式中将分割出与参考图像的偏差大于容许偏差的那些像素值
书中用 m r , c m_{r, c} mr,c 和 s r , c s_{r,c} sr,c 来分别作为模型化参考图像 和 模型化参考图像的容许偏差
m r , c m_{r,c} mr,c = 1 n ∑ i = 1 n g r , c ; i \frac{1}{n} \sum_{i = 1}^{n}g_{r,c; i} n1∑i=1ngr,c;i
s r , c s_{r,c} sr,c = 1 n ∑ i = 1 n ( g r , c ; i − m r , c ) \sqrt{\frac{1}{n} \sum_{i = 1}^{n}(g_{r,c; i - m_{r,c}})} n1∑i=1n(gr,c;i−mr,c)
变差模型需要从n幅训练图像来构造参考图像和偏差图像, 但面对只可能采集一副参考图像的情况下, 建立变差模型有两个方法:
(1) 人为创建一些偏差, 如对图像进行平移
(2) 在物体边缘处存在的相应最大偏差推导出变差模型