图像的阈值处理定义 :将图像转化为二值图像(黑白图), 也可以用于彩色图形,达到夸张的效果
目的:是用来提取图像中的目标物体,将背景和噪声区分开(可以近似的认为除了目标全是噪声)。
阈值处理方法:通常会设定一个阈值 T ,通过 T 将图像的像素划分为两类:大于 T 的像素群和小于 T 的像素群。
- 先将图像转化为灰度图像,因为在灰度图像中,每个像素都只有一个灰度值用来表示当前像素的亮度。
- 接下来二值化处理:即将图像中的像素划分为两类颜色,一种是大于阈值 T 的,另一种是小于阈值 T 的。
应用:图像预处理(滤波)--->图像分割--->图像识别
应用示例:分离对应于我们想要分析的对象的图像的区域。该分离基于对象像素和背景像素之间的强度变化。
为了区分我们感兴趣的像素(其最终将被拒绝),我们对每个像素强度值相对于阈值进行比较(根据要解决的问题确定)。
一旦我们正确分离了重要的像素,我们可以用一个确定的值来设置它们来识别它们(即我们可以为它们分配值(黑色),(白色)或适合您需要的任何值)
在 OpenCV 中,为我们提供了阈值函数 threshold()来帮助我们实现二值图像的处理。
函数如下:
retval, dst = cv2.threshold(src, thresh, maxval, type, dst=None)
- retval: 阈值
- dst: 处理后的图像
- src: 原图像
- thresh: 阈值
- maxval: 最大值
- type: 处理类型
常用的 5 中处理类型如下:
- cv2.THRESH_BINARY: 二值处理
- cv2.THRESH_BINARY_INV: 反二值处理
- cv2.THRESH_TRUNC: 截断阈值化
- cv2.THRESH_TOZERO: 阈值化为 0
- cv2.THRESH_TOZERO_INV: 反阈值化为 0
一、二值化处理(非黑即白)
这种二值处理方式最开始需要选定一个阈值 Threshold,从 0 ~ 255 之间, 这里选择出于中间的那个数127 。
接下来的处理规则就是这样的:
像素值<= 阈值 Threshold: 像素值 = 0,即设定为纯黑色
像素值>= 阈值 Threshold: 像素值 = 最大值
接下来开始写代码
import cv2 as cv
src = cv.imread("maliao.jpg") # BGR 图像转灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值图像处理
Threshold = 127
r, b = cv.threshold(gray_img, Threshold, 255, cv.THRESH_BINARY)
# 显示图像 cv.imshow("src", src) cv.imshow("result", b)
# 等待显示 cv.waitKey(0) cv.destroyAllWindows()
二、 反二值处理
这种方式和上面的二值处理非常相似,只是把处理规则给反了一下:
- 像素值<= 阈值 Threshold: 像素值 =最大值
- 像素值>= 阈值 Threshold: 像素值 = 0,即设定为纯黑色
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 图像转灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值图像处理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY_INV)
# 显示图像
cv.imshow("src", src) cv.imshow("result", b)
# 等待显示 cv.waitKey(0) cv.destroyAllWindows()
从图像上可以看到,颜色和上面的二值图像正好相反,大部分的位置都变成了白色。
三、截断阈值化
这种方法还是需要先选定一个阈值 T ,图像中大于该阈值的像素点被设定为该阈值,小于该阈值的保持不变。
完整代码如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 图像转灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值图像处理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_TRUNC)
# 显示图像 cv.imshow("src", src) cv.imshow("result", b)
# 等待显示 cv.waitKey(0) cv.destroyAllWindows()
这种方式实际上是把图片比较亮的像素处理成为阈值,其他部分保持不变。
四、阈值化为 0
这种方式还是需要先选定一个阈值 T ,将小于 T 的像素点设置为 0 黑色,其他的保持不变。
完整代码如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 图像转灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值图像处理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO)
# 显示图像
cv.imshow("src", src) cv.imshow("result", b)
# 等待显示
cv.waitKey(0) cv.destroyAllWindows()
这个方法是亮的部分不改,把比较暗的部分修改为 0 。
这个和前面的反二值图像很像,同样是反阈值化为 0 ,将大于等于 T 的像素点变为 0 ,其余保持不变。
完整代码如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 图像转灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值图像处理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO_INV)
# 显示图像
cv.imshow("src", src)
cv.imshow("result", b)
# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()
接下来还是给这几种阈值处理后的图像来个全家福,让大家能有一个直观的感受,代码如下:
import cv2 as cv
import matplotlib.pyplot as plt
# 读取图像
img=cv.imread('maliao.jpg')
lenna_img = cv.cvtColor(img,cv.COLOR_BGR2RGB)
gray_img=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 阈值化处理
ret1, thresh1=cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY)
ret2, thresh2=cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY_INV)
ret3, thresh3=cv.threshold(gray_img, 127, 255, cv.THRESH_TRUNC)
ret4, thresh4=cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO)
ret5, thresh5=cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO_INV)
# 显示结果
titles = ['Gray Img','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [gray_img, thresh1, thresh2, thresh3, thresh4, thresh5]
# matplotlib 绘图:可以同时显示多个图像,而且图像显示在命令窗口上,而不是以窗口弹出的方式显
示
for i in range(6):
plt.subplot(2, 3, i+1), plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
举例2:同时显示多种阈值化操作后的图像
import cv2 as cv
import matplotlib.pyplot as plt
# 读取图像
img=cv.imread('C:\\Users\\seewo\\Desktop\\OpenCV\\photo\\beauty11.jpeg')
#lenna_img = cv.cvtColor(img,cv.COLOR_BGR2RGB)
#gray_img=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
gray_img = img
# 阈值化处理
TH = 200
ret1, thresh1=cv.threshold(gray_img, TH, 255, cv.THRESH_BINARY)
ret2, thresh2=cv.threshold(gray_img, TH, 255, cv.THRESH_BINARY_INV)
ret3, thresh3=cv.threshold(gray_img, TH, 255, cv.THRESH_TRUNC)
ret4, thresh4=cv.threshold(gray_img, TH, 255, cv.THRESH_TOZERO)
ret5, thresh5=cv.threshold(gray_img, TH, 255, cv.THRESH_TOZERO_INV)
# 显示结果
titles = ['Gray Img','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [gray_img, thresh1, thresh2, thresh3, thresh4, thresh5]
# 封装图片显示函数:解决关闭窗口后,程序阻塞问题
def showImage(img, title='image', t=3000, esc=False):
cv.imshow(title, img)
if esc:
while cv.waitKey(100) != 27:
if cv.getWindowProperty(title,cv.WND_PROP_VISIBLE)<=0:# 如果图像窗口被
关闭,则不用等待用户按键,直接跳出
break
else:
cv.waitKey(t)
cv.destroyWindow(title) # 跳出后,关闭图像
showImage(thresh1, title='THRESH_BINARY', t=0, esc=False) # 二值化
showImage(thresh2, title='THRESH_BINARY_INV', t=0, esc=False) # 反二值化
showImage(thresh3, title='THRESH_TRUNC', t=0, esc=False) # 截断:图像整体变
暗,降低亮度的同时,浅颜色变得更浅
showImage(thresh4, title='TOZERO', t=0, esc=False) # 阈值化为0(低于阈
值0处理):深颜色沉底变黑,浅颜色不受影响
showImage(thresh5, title='THRESH_TOZERO_INV', t=0, esc=False) # 反阈值化为0(大于
阈值0处理):浅颜色沉底变黑,深颜色不受影响
五、自适应阈值:
当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
cv2.adaptiveThreshold(src, maxValue, adaptive_method, threshold_type, block_size,C )
参数:
src |
原图像,原图像应该是灰度图。 |
maxValue |
像素值高于(有时是小于)阈值时应该被赋予的新的像素值 |
adaptive_method |
自适应阈值计算方法:CV_ADAPTIVE_THRESH_MEAN_C(均值) 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C(高斯加权) |
threshold_type |
阈值类型:CV_THRESH_BINARY 或 CV_THRESH_BINARY_INV |
block_size |
正方形区域:用来计算阈值的象素邻域大小: 3x3, 5x5, 7x7 |
C 常数 |
CV_ADAPTIVE_THRESH_MEAN_C: 块中的均值(mean) - C |
CV_ADAPTIVE_THRESH_MEAN_C: 块中的加权和(gaussian) - C |
在图像阈值化操作中,更关注的是从二值化图像中,分离目标区域和背景区域,但是仅仅通过设定固定阈值很难达到理想的分割效果。
在前面的部分我们使用是全局阈值,整幅图像采用同一个数作为阈值。当时这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
自适应阈值,则是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值。这样做的好处:
- 每个像素位置处的二值化阈值不是固定不变的,而是由其周围邻域像素的分布来决定的。
- 亮度较高的图像区域的二值化阈值通常会较高,而亮度低的图像区域的二值化阈值则会相适应的变小。
- 不同亮度、对比度、纹理的局部图像区域将会拥有相对应的局部二值化阈值。
- 适合处理光照不均的图像。
- Python+opencv代码:
-
import cv2 image = cv2.imread("flower3.jpeg") # 读取4.27.png image_Gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 将4.27.png转换为灰度图像 # 自适应阈值的计算方法为cv2.ADAPTIVE_THRESH_MEAN_C athdMEAM = cv2.adaptiveThreshold\ (image_Gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 3) # 自适应阈值的计算方法为cv2.ADAPTIVE_THRESH_GAUSSIAN_C athdGAUS = cv2.adaptiveThreshold\ (image_Gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 5, 3) # 显示自适应阈值处理的结果 cv2.imshow("MEAN_C", athdMEAM) cv2.imshow("GAUSSIAN_C", athdGAUS) cv2.waitKey() # 按下任何键盘按键后 cv2.destroyAllWindows() # 销毁所有窗口
与上述5种阈值处理方法相比:
- 自适应阈值处理:只能处理灰度图
-
自适应阈值处理:保留了图像中更多的细节信息,更明显地保留了灰度图像主题的轮廓
- 能更好地处理明暗分布不均的图像,获得更简单的图像效果。
-
import cv2 as cv import matplotlib.pyplot as plt # 读取图像 img=cv.imread('beauty4.jpeg') gray_img=cv.cvtColor(img,cv.COLOR_BGR2GRAY) # 5种阈值化处理 ret1, thresh1=cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY) ret2, thresh2=cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY_INV) ret3, thresh3=cv.threshold(gray_img, 127, 255, cv.THRESH_TRUNC) ret4, thresh4=cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO) ret5, thresh5=cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO_INV) # 自适应阈值的计算方法为cv2.ADAPTIVE_THRESH_MEAN_C athdMEAM = cv.adaptiveThreshold(gray_img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 5, 3) # 自适应阈值的计算方法为cv2.ADAPTIVE_THRESH_GAUSSIAN_C athdGAUS = cv.adaptiveThreshold(gray_img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY, 5, 3) # 显示结果 titles = ['Gray Img','BINARY','BINARY_INV','TRUNC','TOZERO',\ 'TOZERO_INV','Adaptive_Mean','Adaptive_Gaussian'] images = [gray_img, thresh1, thresh2, thresh3,\ thresh4, thresh5,athdMEAM,athdGAUS] # matplotlib 绘图:可以同时显示多个图像,而且图像显示在命令窗口上,而不是以窗口弹出的方式显 示 for i in range(8): plt.subplot(2, 4, i+1), plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
自适应阈值处理:保留了图像中更多的细节信息,更明显地保留了灰度图像主题的轮廓
Otsu阈值处理
Ostu是一种阈值选择的算法,在面对色彩分布不均匀的图像时,阈值的选择就会变得很复杂。这时我们就不需要凭借经验去认为设定,而是根据Otsu算法来计算出最合适的阈值。
Ostu的思想很简单,属于暴力寻优的一种,分别计算选用不同灰度级作为阈值时的前景、背景、整体方差。当方差最大时,此时的阈值最好,即遍历所有可能的阈值,从中找到最合适的阈值。
Otsu’s Binarization是一种基于直方图的二值化方法,它需要和threshold函数配合使用。
对于图像二值化的简单阈值法,我们需要自己提供一个阈值,而大津法(OTSU)可以根据图像特性,选择最佳的阈值,故它也被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。
它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
应用: 是求图像全局阈值的最佳方法,应用不言而喻,适用于大部分需要求图像全局阈值的场合。
优点: 计算简单快速,不受图像亮度和对比度的影响。
缺点: 对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。
Otsu过程:
- 计算图像直方图;
- 设定一阈值,把直方图强度大于阈值的像素分成一组,把小于阈值的像素分成另外一组;
- 分别计算两组内的偏移数,并把偏移数相加;
- 把0~255依照顺序多为阈值,重复1-3的步骤,直到得到最小偏移数,其所对应的值即为结果阈值。
代码展示
选择一张偏暗的图片来测试程序。
import cv2 as cv
import matplotlib.pyplot as plt
image = cv.imread("beauty5.jpeg", cv.IMREAD_GRAYSCALE)
ret1, dst1 = cv.threshold(image, 127, 255, cv.THRESH_BINARY) # 二值化阈值处理
ret2, dst2 = cv.threshold(image, 0 , 255, cv.THRESH_OTSU) # Otsu方法
dst3 = cv.adaptiveThreshold(image, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 5, 3) # 自适应阈值
#cv.imshow("image", image)
#cv.imshow("threshold", dst1)
#cv.imshow("otsu", dst2)
#cv.imshow("GAUSSIAN_C", dst3)
titles = ['gray_orgin','Binary','Otsu','Adaptive_Gaussian']
images = [image,dst1,dst2,dst3]
for i in range(4):
plt.subplot(2,2,i+1), plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
cv.waitKey()
cv.destroyAllWindows()