第四章:OpenCv阈值分割/二值化(单通道、多通道图片)总结
0.前言
- 对一些算法的基本概念以及原理,本文不做过多的阐述,本文将一些相关基础知识以超链接的形式放在文章开头,大家根据需要进行挑选阅读,有基础的朋友可以直接跳过。
1.基础知识
1.1 什么是算子
-
什么是算子: 高等数学上册P2中有这样的描述,算子即映射。可理解为从图像中的离散像素点集合通过某种关系,映射到另一个集合即可。
1.2 图像处理中的图像分割技术
-
对于图像处理中常见的分割,我这里做了个人的总结,同时,第三章主要以阈值分割为主,以及一些技巧的分享。在后面章节,会分享粘连物体的分割。
1.3 单通道图片的阈值分割
-
注意:只要是单通道图片,就能进行二值化。 这里的单通道图片主要以Gray灰度图为主,但是在一些检测当中,需要用到其他色彩空间某些单通道的图片进行分割(因为某通道特征可能更为明显,更好分割)。后文会贴上程序,如何分离一张图片所有色彩空间下所有单通道图片。
-
下面展示的是常用在单通道图片阈值分割的二值化算法
1.4 推荐文章链接
1.多种色彩空间RGB、BGR、HSV /HSB、HSL、YUV的基本简介
2.多种边缘检测算法(Sobel算子、Isotropic Sobel算子、Roberts算子、Prewitt算子、Laplacian算子、Canny算子)介绍及比较
3.图像二值化方法汇总
4.图像二值化
5.多阈值 OTSU 处理方法
6.大津二值化
7.《opencv算法精解》全书笔记
8.opencv手册4.0中文文档
2. 灰度图分割
- 个人比较常用的二值化算法是:简单二值化、大津二值化算法(图片亮度不均匀也能比较好分割)。其中简单二值化选取的阈值有时候可以借助灰度直方图,可以使阈值选取更为恰当,
- 当图片有以下特点时可以考虑用灰度图分割: 检测物体对色彩特征没有要求,同时物体与背景像素值相差比较大。
2.1 灰度直方图
# 作者:OpenCv机器视觉
# 时间:2023/1/5
# 功能:灰度、BGR像素直方图
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 绘制灰度直方图
def plot_demo(image):
"""
画灰度图直方图:
绘图都可以调用matplotlib.pyplot库来进行,其中的hist函数可以直接绘制直方图。
plt.hist(arr, bins=50, normed=1, facecolor='green', alpha=0.75)
hist的参数非常多,但常用的就这五个,只有第一个是必须的,后面四个可选
arr: 需要计算直方图的一维数组
bins: 直方图的柱数,可选项,默认为10
normed: 是否将得到的直方图向量归一化。默认为0
range参数表示箱子的下限和上限。即横坐标显示的范围,范围之外的将被舍弃
"""
plt.hist(image.ravel(), 256, [0, 256],color="black") # image.ravel()#ravel函数功能是将多维数组降为一维数组,统计各个bin的频次,256:bin的个数,[0, 256]:范围
plt.show() # 和OpenCV中的想要的直方图不同
def image_hist(image):
"""
calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
images参数表示输入图像,传入时应该用中括号[ ]括起来
channels参数表示传入图像的通道,如果是灰度图像,那就不用说了,只有一个通道,值为0,
如果是彩色图像(有3个通道),那么值为0,1,2,中选择一个,对应着BGR各个通道。这个值也得用[ ]传入。
mask参数表示掩膜图像。如果统计整幅图,那么为None。
主要是如果要统计部分图的直方图,就得构造相应的掩膜来计算。
histSize参数表示灰度级的个数,需要中括号,比如[256]
ranges参数表示像素值的范围,通常[0,256]。此外,假如channels为[0,1],ranges为[0,256,0,180],
则代表0通道范围是0-256,1通道范围0-180。
hist参数表示计算出来的直方图。
"""
color = ('blue', 'green', 'red') # 图像三通道
for i, color in enumerate(color):
hist = cv.calcHist([image], [i], None, [256], [0, 256]) # 绘制各个通道的直方图
plt.plot(hist, color=color) # 定义线的颜色
plt.xlim([0, 256]) # x轴的范围
plt.show()
src = cv.imread("img/1.png")
cv.namedWindow("input image",0)
cv.imshow("input image", src)
gray = cv.cvtColor(src,cv.COLOR_BGR2GRAY)
# 灰度直方图
plot_demo(gray)
# BGR像素直方图
image_hist(src)
cv.waitKey(0)
cv.destroyAllWindows()
-
图片特征分析:不需要颜色特征,所以直接对其灰度图分割即可。根据灰度像素直方图可得我们得阈值设置在40-125左右即可。
图2.1 灰度像素直方图
2.2 简单阈值分割与大津二值化分割
- 代码,这里包含了简单阈值分割,自适应阈值分割、大津分割算法代码(代码涉及到python面向对象的知识点)。
# 作者:OpenCv机器视觉
# 时间:2023/1/5
# 功能:简单阈值分割,自适应阈值分割、大津分割算法代码
import cv2 as cv
class GetBinary(object):
def __init__(self,img):
self.img = img
def threshed_fixed(self):
"""
:function:利用固定阈值进行二值化
:return:
"""
blurred = cv.blur(self.img, (5,5)) # 这里进行5x5卷积核的均值滤波处理
gray = cv.cvtColor(blurred,cv.COLOR_BGR2GRAY)
ret,bin = cv.threshold(gray,100,255,cv.THRESH_BINARY)
return bin
def adaptive_threshold(self):
"""
:function:利用自适应局部二值化方法进行二值化
:return:
"""
blurred = cv.blur(self.img, (5,5)) # 这里进行5x5卷积核的均值滤波处理
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
bin = cv.adaptiveThreshold(src=gray,maxValue=255,adaptiveMethod=cv.ADAPTIVE_THRESH_MEAN_C,thresholdType=cv.THRESH_BINARY,blockSize=31,C=1)
return bin
def threshed_otsus(self):
"""
:function:利用OTSU's二值化算法阈值进行二值化
:return:
"""
blurred = cv.blur(self.img, (5, 5)) # 这里进行5x5卷积核的均值滤波处理
gray = cv.cvtColor(blurred,cv.COLOR_BGR2GRAY)
ret,bin= cv.threshold(gray,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
return bin
img = cv.imread(".\\img\\1.png")
# 定义一个对象
get_binary = GetBinary(img)
# 调用对象中的方法
bin1 = get_binary.threshed_fixed() # 简单阈值分割
bin2 = get_binary.adaptive_threshold() # 自适应阈值分割
bin3 = get_binary.threshed_otsus() # 大津算法分割
cv.namedWindow("img",0)
cv.imshow("img",img)
cv.namedWindow("threshed_fixed",0)
cv.imshow("threshed_fixed",bin1)
cv.namedWindow("adaptive_threshold",0)
cv.imshow("adaptive_threshold",bin2)
cv.namedWindow("threshed_otsus",0)
cv.imshow("threshed_otsus",bin3)
cv.waitKey(0)
cv.destroyAllWindows()
- 参考链接
3.分离常见色彩空间下的单通道图片
- 背景意义:当彩色图片下的目标与背景区分度不是很高,这时候我们可以考虑分离各通道下的图片,观察在哪个通道下的特征比较明显,便于后期的阈值分割。这里举例子的是某课题细胞分割图片。
- 代码
# 作者:OpenCv机器视觉
# 时间:2023/1/6
# 功能:获取HSV 、LAB、HIS、LUV、YUV色彩空间下各单通道图片
import cv2 as cv
import os
def split_all_chancel(img):
# 色彩空间转换
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
HSV = cv.cvtColor(img, cv.COLOR_BGR2HSV)
LAB = cv.cvtColor(img, cv.COLOR_BGR2LAB)
HIS = cv.cvtColor(img, cv.COLOR_BGR2HLS)
LUV = cv.cvtColor(img, cv.COLOR_BGR2LUV)
YUV = cv.cvtColor(img, cv.COLOR_BGR2YCrCb)
# 对各图片通道进行拆分
bgr_b, bgr_g, bgr_r = cv.split(img)
hsv_h, hsv_s, hsv_v = cv.split(HSV)
lab_l, lab_a, lab_b = cv.split(LAB)
his_h, his_i, his_s = cv.split(HIS)
luv_l, luv_u, luv_v = cv.split(LUV)
yuv_y, yuv_u, yuv_v = cv.split(YUV)
return ([hsv_h, hsv_s, hsv_v,lab_l, lab_a, lab_b, his_h, his_i, his_s,luv_l, luv_u, luv_v,yuv_y, yuv_u, yuv_v,bgr_b, bgr_g, bgr_r,gray],
["hsv_h"," hsv_s", "hsv_v","lab_l", "lab_a", "lab_b", "his_h", "his_i", "his_s","luv_l", "luv_u", "luv_v","yuv_y", "yuv_u", "yuv_v","bgr_b","bgr_g","bgr_r","gray"])
# 原图片路径
img_path = ".\\img\\2.jpg"
img = cv.imread(img_path) # 读取图片
name = os.path.splitext(img_path)[0] # 图片文件名
extend_name = os.path.splitext(img_path)[-1] # 图片扩展名
if not os.path.exists(name): # 判断以图片名 为命名的文件夹是否存在
os.makedirs(name) # 不存在就创建文件夹,用于存放图片
# 调用分离通道函数
channels_imgs,channel_str = split_all_chancel(img)
for i, channels_img in enumerate(channels_imgs):
out_name = os.path.join(name,channel_str[i]+".png") # 保存路径,为图片相同路径下
print(out_name)
cv.imwrite(out_name, channels_img)
- 通道分离效果如下,从分离的单通道图片中,不难看出,HSV色彩空间下的V通道特征比较明显(BGR_G,LAB_L通道也比较明显,都可以采用此通道进行阈值分割,但是常用的还是HSV色彩空间),因此下文通过HSV色彩空间转换,对细胞进行分割识别。
4. HSV分割
4.1 常见分割代码
- HSV的上下阈值根据 HSV颜色分量范围表 来确定
- 因为细胞是偏绿色,所以这里根据颜色分量范围表进行设置高低阈值
lower = np.array([35, 43, 46])
upper = np.array([77, 255, 255])
- 代码:
# 作者:OpenCv机器视觉
# 时间:2023/1/6
# 功能:根据HSV颜色分量表进行阈值分割识别
import cv2 as cv
import numpy as np
path = ".\\img\\2.jpg" # 图片位置
img = cv.imread(path) #读取图像
imgHsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) #转换为HSV色彩空间
lower = np.array([35, 43, 46]) #定义低阈值
upper = np.array([77, 255, 255]) #定义高阈值
mask = cv.inRange(imgHsv,lower,upper) #获取分割掩膜图
imgResult = cv.bitwise_and(img,img,mask,mask=mask) # 将掩膜图与原图进行运算,白色区域目标显示为彩色效果
contours = cv.findContours(mask.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)[0] # 获取图片的轮廓
for c in contours: # 获取每个轮廓
x, y, w, h = cv.boundingRect(c)
cv.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2 ) # 画外接矩形
cv.namedWindow("mask",0)
cv.imshow("mask",mask)
cv.namedWindow("img", 0)
cv.imshow("img", img)
cv.namedWindow("imghsv",0)
cv.imshow("imghsv", imgHsv)
cv.waitKey(0)
- 分割效果:对颜色较为明显的细胞能识别到,但是对于染色不为明显的细胞,漏识别。难道阈值我们就要根据这个表去一个个尝试吗?这样效率太低了,下面会介绍一款非常好用的滑动条工具。
4.2 技巧1,通过鼠标感应,获取某个像素的的HSV像素值
- 该操作主要了解以下鼠标感应,获取图像某位置的信息。要更好地进行目标识别,还是需要利用技巧2中的滑动条。
# 作者:OpenCv机器视觉
# 时间:2023/1/6
# 功能:通过鼠标感应,点击鼠标左键,获取该坐标的相关信息,可以是BGR值,可以是坐标,也可以是灰度值,这里是以HSV值为例,大家可以根据想法更改程序
import cv2 as cv
# 图片路径
img = cv.imread("img\\2.jpg")
def on_EVENT_LBUTTONDOWN(event, x, y, flags, param):
if event == cv.EVENT_LBUTTONDOWN:
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) # 图片重新加载,用于刷新,保证上一步的显示内容不被显示
xy = "%d,%d" % (x, y)
print("坐标:",(x,y),"HSV值",hsv[y][x]) # hsv[y][x]因为图片第一个参数是高,第二个参数是宽
cv.putText(hsv, xy, (x, y), cv.FONT_HERSHEY_PLAIN,2, (0, 0, 255), thickness=2)
cv.imshow("HSV", hsv)
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV) # 色彩空间转换
cv.namedWindow("HSV",0)
cv.setMouseCallback("HSV", on_EVENT_LBUTTONDOWN)
cv.imshow("HSV", hsv)
cv.waitKey(0)
4.2 最佳技巧性工具:借助滑动条,快速实现基于颜色的目标识别
- 代码
# 作者:OpenCv机器视觉
# 时间:2023/1/6
# 功能:借助滑动条进行颜色识别
import cv2 as cv
import numpy as np
path = ".\\img\\2.jpg" # 图片位置
def empty():
pass
cv.namedWindow("TrackBars",0)
cv.createTrackbar("Hue Min","TrackBars",0,179,empty)
cv.createTrackbar("Hue Max","TrackBars",179,179,empty)
cv.createTrackbar("Sat Min","TrackBars",0,255,empty)
cv.createTrackbar("Sat Max","TrackBars",255,255,empty)
cv.createTrackbar("Val Min","TrackBars",0,255,empty)
cv.createTrackbar("Val Max","TrackBars",255,255,empty)
while True:
# 获取滑动条参数值
h_min = cv.getTrackbarPos("Hue Min","TrackBars")
h_max = cv.getTrackbarPos("Hue Max", "TrackBars")
s_min = cv.getTrackbarPos("Sat Min", "TrackBars")
s_max = cv.getTrackbarPos("Sat Max", "TrackBars")
v_min = cv.getTrackbarPos("Val Min", "TrackBars")
v_max = cv.getTrackbarPos("Val Max", "TrackBars")
img = cv.imread(path) #读取图像
print("lower = np.array("+str([h_min,s_min,v_min])+")")
print("upper = np.array("+str([h_max,s_max,v_max])+")")
imgHsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) #转换为HSV色彩空间
lower = np.array([h_min,s_min,v_min]) #定义低阈值
upper = np.array([h_max,s_max,v_max]) #定义高阈值
mask = cv.inRange(imgHsv,lower,upper) #获取灰度图
imgResult = cv.bitwise_and(img,img,mask,mask=mask) #目标显示RGB
# 对识别后的图像进行框选
area = [] # 定义一个列表来图片的面积
contours = cv.findContours(mask.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)[0] # 获取图片的轮廓 ,不同python有的索引值是0,有的是1
if h_min>0 or v_min>0 or s_min>0 or h_max<180 or s_max<255 or v_max<255: # 当检测到有拖动的时候再开始轮廓的面积计算,因为有的图片初始化没有发现轮廓,计算面积就会报错,
for c in contours: # 获取每个轮廓
if cv.contourArea(c)>10: # 进行滤波,将一些太小的噪点过滤掉
x, y, w, h = cv.boundingRect(c)
cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 255), 2 ) # 画外接矩形
cv.namedWindow("mask",cv.WINDOW_NORMAL)
cv.imshow("mask",mask)
cv.namedWindow("img", cv.WINDOW_NORMAL)
cv.imshow("img", img)
cv.waitKey(1)
- 识别效果
视频4.2 滑动条识别效果
5 总结
-
本章节主要内容为阈值分割
1)提到了图像分割方法;
2)常用的二值化方法、如何根据需求进行通道分类,进行阈值分割
3)推荐了颜色识别当中最好用的滑动条工具。
4)以及个人整理的源代码 -
本文没有详细介绍原理,多为个人学习总结,底层原理内容多以超链接形式供大家选择阅读。因个人总结可能有点漏缺,如有错误地方欢迎大家批评指正。原创不易,如果大家觉得文章不错,欢迎点赞收藏!!!
6. 其他章节链接
1.理论系列:
第一章:pycharm、anaconda、opencv、pytorch、tensorflow、paddlex等环境配置大全总结【图像处理py版本】
第二章:OpenCv算法的基本介绍与应用
第三章:OpenCv图片、视频读写操作与基本应用
==》第四章:OpenCv阈值分割/二值化(单通道、多通道图片)总结
2.项目系列:
项目一:四六级改卷系统
项目二:实战篇:粘连物体分割——利用几何分割实现瓶盖分割检测
项目三:实战篇:粘连物体分割——利用几何分割实现硬币分割检测
项目四:实战篇:粘连物体分割——利用几何分割实现细胞分割检测
项目五:实战篇:粘连物体分割——利用分水岭算法实现糖豆分割检测