读书笔记-opencv-阈值分割
阈值分割处理主要是灰度值信息提取前景,所以对前景物体与背景物体有较强的对比度的图像的分割特别有用,对对比度很弱的图像进行阈值分割,需要先进行对比度增强,在进行阈值处理。常用的两种:全局阈值分割和自适应局部阈值分割。
全局阈值分割
全局阈值分割指的是将灰度值大于thresh(阈值)的图像设为白色,小于或者等于thresh的像素设为黑色;或者反只。
假设输入图像为I,高为H,宽为W,I(r,c)代表第r行第c列的灰度值,0<=r <H, 0<=c<W,全局阈值处理后的输出图像为O, O(r,c)代表第r行第c列的灰度值,则:
O(r,c)={
255,I(r,c)>thresh0,I(r,c)≤thresh O(r,c)= \begin{cases} 255,I(r,c)>thresh \\ 0,\quad I(r,c) \leq thresh\end{cases} O(r,c)={
255,I(r,c)>thresh0,I(r,c)≤thresh
或者:
O(r,c)={
0,I(r,c)>thresh255,I(r,c)≤thresh O(r,c)= \begin{cases} 0,\quad I(r,c)>thresh \\ 255, I(r,c) \leq thresh\end{cases} O(r,c)={
0,I(r,c)>thresh255,I(r,c)≤thresh
src[src>150]= 255
src[src<=150]= 0
图像处理不改变原图,先将原图进行复制,再进行操作,opencv提供threshold()函数:
threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
src:单通道矩阵,数据类型为CV_8U或者CV_32F;
dst输出矩阵,即阈值分割后的矩阵; thresh: 阈值
maxVal:在图像二值化显示时,一般设为255
type:类型查看枚举类型ThresholdTypes
enum ThresholdTypes{THRESH_BINARY = 0, THRESH_BINARY_INV = 1,
THRESH_TRUNC = 2, THRESH_TOZERO = 3,
THRESH_TOZERO_INV = 4, THRESH_MASK = 7,
THRESH_OTSU = 8, THRESH_TRIANGLE = 16}
注意类型为THRESH_OSTU或THRESH_TRIANGLE时,输入参数只支持uchar类型,这是thresh也是作为输出参数的,通过Otsu和TRIANGLE算法自动计算出来的,这两种类型和其他类型配合使用,如设置type = THRESH_OSTU + THRESH_BINARY,先利用THRESH_OSTU计算出阈值,才利用THRESH_BINARY进行阈值分割。THRESH_TRIANGLE和后面的直方图阈值处理原理类似,后面会进行介绍这两种算法。
if __name__ == "__main__":
imagePath = "G:\\blog\\OpenCV_picture\\chapter4\\img4.jpg"
src = cv2.imread(imagePath, 0)
#手动设置阈值
thresh = 60
maxVal = 255
#注意这里返回的也是两个值,若是不用ret来接受返回值,会报错
ret, dst = cv2.threshold(src, thresh, maxVal, cv2.THRESH_BINARY)
#THRESH_OTSU和THRESH_TRIANGLE默认和THRESH_BINARY搭配使用
#Otsu阈值处理
otsuThresh = 0
otsuThresh, dstOtsu = cv2.threshold(src, otsuThresh, maxVal, cv2.THRESH_OTSU)
#TRIANGLE阈值处理
triThresh = 0
triThresh, dstTriangle = cv2.threshold(src, triThresh, maxVal, cv2.THRESH_TRIANGLE)
#显示原图和阈值处理后的的图片
cv2.imshow("src", src)
cv2.waitKey(0)
cv2.imshow("dst", dst)
cv2.waitKey(0)
cv2.imshow("dstOtsu", dstOtsu)
cv2.waitKey(0)
cv2.imshow("dstTriangle", dstTriangle)
cv2.waitKey(0)
cv2.destroyAllWindows()
int main()
{
//输入图像
std::string imagePath = "G:\\blog\\OpenCV_picture\\chapter4\\img4.jpg";
Mat src = imread(imagePath, 0);
if (!src.data)
{
std::cout << "load image error!" << std::endl;
return -1;
}
//手动设置阈值
double thresh = 60;
double maxValue = 255;
Mat dst;
threshold(src, dst, thresh, maxValue, THRESH_BINARY);
//Otsu设置阈值
double OtsuThresh = 0.0;
Mat OtsuMat;
OtsuThresh = threshold(src, OtsuMat, OtsuThresh, maxValue, THRESH_OTSU + THRESH_BINARY);
//TRIANGLE设置阈值
double TriThresh = 0.0;
Mat TriMat;
TriThresh = threshold(src, TriMat, TriThresh, maxValue, THRESH_TRIANGLE + THRESH_BINARY);
//显示图像
imshow("src", src);
imshow("dst", dst);
imshow("OtsuMat", OtsuMat);
imshow("TriMat", TriMat);
cout << "OtsuThresh: " << OtsuThresh << " " << "TriThresh: " << OtsuThresh << endl;
waitKey(0);
return 0;
}
运行图片
局部阈值分割
在比较理想的情况下,对整个图像使用单阈值才会成功,在受到光照等其他因素的影响下,全局分割阈值往往不理想,需要局部阈值分割(自适应阈值分割)规则如下:
O(r,c)={
255,I(r,c)>thresh(r,c)0,I(r,c)≤thresh(r,c) O(r,c) = \begin {cases}255, \quad I(r,c)>thresh(r,c) \\ 0, \quad\quad I(r,c) \leq thresh(r,c) \end{cases} O(r,c)={
255,I(r,c)>thresh(r,c)0,I(r,c)≤thresh(r,c)
或者
O(r,c)={
0,I(r,c)>thresh(r,c)255,I(r,c)≤thresh(r,c) O(r,c) = \begin {cases}0, \quad\quad I(r,c)>thresh(r,c) \\ 255, \quad I(r,c) \leq thresh(r,c) \end{cases} O(r,c)={
0,I(r,c)>thresh(r,c)255,I(r,c)≤thresh(r,c)
不像全局阈值只有一个阈值,二十针对每个输入矩阵的元素都有一个阈值,构成相同尺寸的矩阵thresh。
局部阈值分割的核心也是计算阈值矩阵。比较常用的是自适应阈值算法(移动平均值算法)。核心思想是每个像素邻域的“平均值”作为该位置的阈值。
直方图技术法
当一幅图像有一个与背景呈现明显对比的物体的图像会具有包含双峰的直方图,两个峰值对应物体内部和外部较多数目的点两个峰值之间的波谷对应于物体边缘附近相对较少的点。而直方图技术则是根据这一“双峰”特点计算的。首先要找到这两个峰值,然后取两个峰值之间的波谷对应的灰度值,这个灰度值就是阙值,我们采用如下的算法来寻找直方图中的波谷。由于灰度值在直方图中随机波动,在双峰之间可能出现两个较小值,通过鲁棒性选定最小值对应的阈值。一种常见的方式:先对直方图进行高斯平滑,逐渐增大高斯滤波的标准差,知道得到两个直方图的两个唯一波峰和他们之间的最小值。这种需要手动调节,下面介绍的是自动选取波峰和波谷。
假设输入图像为I,高位H,宽为W histogramIhistogram_{I}histogramI代表其对应的灰度直方图,histogramI(k)histogram_{I}(k)histogramI(k)代表灰度值等于k的像素点个数,其中0≤k≤2550\leq k\leq2550≤k≤255
第一步:找到灰度直方图的第一个峰值,并找到其对应的灰度值也就是该峰值对应的位置,显然,第一个峰值就是灰度直方图的最大值,它对应的灰度值使用firstPeak表示。
第二步:找到灰度直方图的第二个峰值,并找到其对应的灰度值。注意:第二峰值不一定是直方图的第二大值,因为我们需要的峰值是希望在一定邻域内是最大值,如果第二大值和第一大值出现的很近,那么这种情况下第二大值就很明显不是一个峰值。使用如下公式计算最大峰值,
secondPeak=argk{
(k−firstPeak)2∗histogramI(k)}0≤k≤255 secondPeak=arg_k\{(k-firstPeak)^2*histogram_I(k)\} \quad\quad0\leq k\leq 255 secondPeak=argk{
(k−firstPeak)