实验原理与步骤
1.连通定义
在数字图像中,一个像素在空间上可能非常接近其它一些像素。在用网格表示的数字图像中,一个像素与其它四个像素有公共边界,并与另外四个像素共享顶角。如果两个像素有公共边界,则称它们互为4邻点(4-neighbors)。同样,如果两个像素至少共享一个顶点,则称它们互为8邻点,如图1所示:
2. 连通成分标记
在一幅图像中找出连通成分是数字图像中最常见的运算之一。连通区域内的点构成表示物体的候选区域。机器视觉中的大多数物体都有表面,显然,物体表面点投影到图像平面上会形成空间上密集的点集。这里应该指出,连通成分算法常常会在二值视觉系统中形成瓶颈效应,原因是连通成分运算是一个全局性的运算,这种算法在本质上是连贯的。如果图像中仅有一个物体,那么找连通成分就没有必要;如果图像中有许多物体,且需要求出物体的特性与位置,则必须确定连通成分。
连通标记算法可以找到图像中的所有连通成分,并对同一连通成分中的所有点分配同一标记。图2表示的是一幅图像和已标记的连通成分。
3. 递归算法
算法步骤如下:
- ① 扫描图像,找到没有标记的1点,给它分配一个新的标记L;
- ② 递归分配标记L给1点的邻点;
- ③ 如果不存在没标记的点,则停止;
- ④ 返回第①步。
4.区域分裂算法
如果区域的某些特性不是恒定的,则区域应该分裂。基于分裂方法的图像分割过程是从最大的区域开始,在许多情况下,常把整个图像作为起始分裂的图像。
算法步骤如下:
- ① 形成初始区域;
- ② 对图像的每一个区域,连续执行下面两步:
计算区域灰度值方差;
如果方差值大于某一阈值,则沿着某一适合的边界分裂区域。
5.区域增长算法
在许多图像中,单个区域内的灰度值不是完全恒定的,因此需要更复杂的算法来进行图像分割。其中最好的算法是那些基于如下假设的算法,即图像可以划分成区域,而区域可以用简单函数模型化。
可由分割问题导出如下算法:寻找初始区域核,并从区域核开始,逐渐增长核区域,形成满足一定约束的较大的区域。
算法步骤如下:
- ① 把图像划分成初始区域核;
- ② 用平面模型拟合每一个区域核;
- ③ 对每一个区域,通过区域模型向邻接区域外插,求取与该区域兼容的所有点;
- ④ 如果没有兼容点,则增加模型阶数。如果模型阶数大于最大的模型阶数,停止区域增长;否则,回到第③步,继续区域增长;
- ⑤ 形成新的区域,重新用相同阶数的模型能够拟合新区域,计算拟合最佳度;
- ⑥ 计算区域模型的新老拟合最佳度之差;
- ⑦ 如果新老拟合最佳度之差小于某一给定阈值,回到第三步,继续区域增长;
- ⑧ 增加模型阶数,如果模型阶数大于最大的模型阶数,停止区域增长;
- 用新的模型阶数再拟合区域模型。入伙拟合误差增加,接收新的模型阶数,回到第③步,继续区域增长,否则,停止区域增长。
算法设计思路
(1)、首先将图片转换成灰度图进行操作,先进行全局阈值的计算,最适合的全局阈值计算使用otsu(大津法计算)
- ① 先计算图像的直方图,即将图像所有的像素点按照0~255共256个bin,统计落在每个bin的像素点数量
- ② 归一化直方图,也即将每个bin中像素点数量除以总的像素点
- ③ i表示分类的阈值,也即一个灰度级,从0开始迭代
- ④ 通过归一化的直方图,统计0~i 灰度级的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例w0,并统计前景像素的平均灰度u0;统计i~255灰度级的像素(假设像素值在此范围的像素叫做背景像素) 所占整幅图像的比例w1,并统计背景像素的平均灰度u1;
- ⑤计算前景像素和背景像素的方差 g = w0w1(u0-u1) (u0-u1)
- ⑥i++;转到 4 ,直到i为256时结束迭代
- ⑦将最大g相应的i值作为图像的全局阈值
#使用otsu算法思维求出阈值并对图像进行二值化处理
def myotsu(gray):
countdown = 0
countup = 0
hist_new = []
num = []
hist_countresult = []
hist_key = []
hist_proresult = []
#处理后最终输出矩阵将齐大小设置为与原图一样
gray2=np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype='float')
#gray1 用于统计每哥灰度级所有的个数 ,因为是列表不是矩阵,
#所以要先将gray的灰度级矩阵变成一维列表
gray1 = list(gray.ravel())
#以字典的形式保存,统计出来的灰度级及其个数
obj = dict(collections.Counter(gray1))
obj = sorted(obj.items(),key=lambda item:item[0])
#将统计出来的灰度级的值与他的个数分开用列表保存
for each in obj :
key = list(each)[0]
num =list(each)[1]
hist_key.append(key)
hist_new.append(num)
#检查从0-255每个灰度级是否都有个数,没有的话添加并将值设为0
for i in range (0,256) :
if i in hist_key :
num = hist_key.index(i)
hist_countresult.append(hist_new[num])
else :
hist_countresult.append(0)
if len(hist_countresult) < 256 :
for i in range (0,256-len(hist_countresult)) :
hist_new.append(0)
#计算整幅图的像素数目
hist_sum = gray.shape[0] * gray.shape[1]
#计算每个灰度级的像素数目占整个数目的比重
for each in hist_countresult :
result = float(each / hist_sum)
hist_proresult.append(result)
#遍历灰度级[0,255],寻找合适的threshold
w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = deltaMax = float(0)
for i in range (256) :
w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = float(0)
for j in range (256) :
if j <= i : #背景部分
w0 = float(w0 + hist_proresult[j])
u0tmp += j * hist_proresult[j]
else : #前景部分
w1 += float(hist_proresult[j])
u1tmp += j * hist_proresult[j]
if w0 == 0.0 or w1 == 0.0:
pass
else :
u0 = float(u0tmp / w0)
u1 = float(u1tmp / w1)
deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2))
if deltaTmp > deltaMax :
deltaMax = deltaTmp
threshold = i
#用ostu大津算法得出最适当的阈值后,将图片进行二值化
for i in range(gray.shape[0]) :
for j in range(gray.shape[1]) :
#对大于阈值的显示为255白色,小于阈值的显示为0黑色
if gray[i][j] <= threshold :
gray2[i][j] = 0
countdown += 1
else :
gray2[i][j] = 255
countup += 1
return gray2
(2)、动态阈值,将原灰度图片,按照几等分的切割规律,将每一个小方格里的像素取出来,存到一个列表里,之后在进行取出,参照规律。然后按照阈值来进行二值化
#分割成为多块区域
def divide(gray,m,n) :
height,width = gray.shape[0],gray.shape[1]
each_width = int(width / (m-1))
each_height = int(height / (n-1))
#生成存放16块图像灰度值的列表
divide_list = []
for i in range (m-1) :
for j in range (n-1):
#生成存放每块小图片灰度级的矩阵
divide_image = np.array([[0 for i in range(each_width)] for i in range(each_height)], dtype=np.uint8)
for divide_i in range (each_height):
for divide_j in range (each_width) :
divide_image[divide_i][divide_j] = gray[ i*each_height + divide_i ][ j*each_width + divide_j]
divide_list.append(divide_image)
return divide_list
#*********************************
# 动态阈值分割
#*********************************
#创建动态阈值矩阵
Dynamic_threshold_image = np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype=np.uint8)
#调用自己写的otsu算法,进行阈值化分割图像处理
m=4
n=4
each_width = int(width / (m))
each_height = int(height / (n))
divide_list = divide(gray,m+1,n+1) #分割成为4*4的块
#用ostu算法计算每一小块图片的最佳阈值,进行动态阈值处理
num = 0 #记录这是第几块图形,第一块为0
for each in divide_list :
#根据第几幅图算出他在原图中所在的行号和列号
r = int (num / m) #行
c = num % n #列
ostu_image = myotsu(each)
for i in range (ostu_image.shape[0]) :
for j in range (ostu_image.shape[1]) :
Dynamic_threshold_image[ i + r*each_height ][ j + c*each_width ] = ostu_image [i][j]
num += 1
cv2.imshow('Dynamic_threshold',Dynamic_threshold_image)
(3)、动态生长:
- ① 对图像顺序扫描!找到第1个还没有归属的像素, 设该像素为(x0, y0);
- ②以(x0, y0)为中心, 考虑(x0, y0)的4邻域像素(x, y)如果(x0, y0)满足生长准则, 将(x, y)与(x0, y0)合并(在同一区域内), 同时将(x, y)压- 入堆栈;
- ③从堆栈中取出一个像素, 把它当作(x0, y0)返回到步骤2;
- ④当堆栈为空时!返回到步骤1;
- ⑤重复步骤1 - 4直到图像中的每个点都有归属时。生长结束。
#求两个点的差值
def getGrayDiff(image,currentPoint,tmpPoint):
return abs(int(image[currentPoint[0],currentPoint[1]]) - int(image[tmpPoint[0],tmpPoint[1]]))
#区域生长算法
def regional_growth (gray,seeds,threshold=15) :
#每次区域生长的时候的种子像素之间的八个邻接点
connects = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), \
(0, 1), (-1, 1), (-1, 0)]
threshold = threshold #种子生长时候的相似性阈值,默认即灰度级不相差超过15以内的都算为相同
height, weight = gray.shape
seedMark = np.zeros(gray.shape)
seedList = []
for seed in seeds:
seedList.append(seed) #将种子添加到种子的列表中
label = 1 #标记点的flag
while(len(seedList)>0): #如果种子列表里还存在种子点
currentPoint = seedList.pop(0) #将最前面的那个种子抛出
seedMark[currentPoint[0],currentPoint[1]] = label #将对应位置的点标志为1
for i in range(8): #对这个种子点周围的8个点一次进行相似性判断
tmpX = currentPoint[0] + connects[i][0]
tmpY = currentPoint[1] + connects[i][1]
if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= weight: #如果超出限定的阈值范围
continue #跳过并继续
grayDiff = getGrayDiff(gray,currentPoint,(tmpX,tmpY)) #计算此点与种子像素点的灰度级之差
if grayDiff < threshold and seedMark[tmpX,tmpY] == 0:
seedMark[tmpX,tmpY] = label
seedList.append((tmpX,tmpY))
return seedMark
完整代码:
import matplotlib.pyplot as plt
from scipy import signal
import numpy as np
import copy as cp
import random
import math
import cv2
import collections
#使用otsu算法思维求出阈值并对图像进行二值化处理
def myotsu(gray):
countdown = 0
countup = 0
hist_new = []
num = []
hist_countresult = []
hist_key = []
hist_proresult = []
#处理后最终输出矩阵将齐大小设置为与原图一样
gray2=np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype='float')
#gray1 用于统计每哥灰度级所有的个数 ,因为是列表不是矩阵,
#所以要先将gray的灰度级矩阵变成一维列表
gray1 = list(gray.ravel())
#以字典的形式保存,统计出来的灰度级及其个数
obj = dict(collections.Counter(gray1))
obj = sorted(obj.items(),key=lambda item:item[0])
#将统计出来的灰度级的值与他的个数分开用列表保存
for each in obj :
key = list(each)[0]
num =list(each)[1]
hist_key.append(key)
hist_new.append(num)
#检查从0-255每个灰度级是否都有个数,没有的话添加并将值设为0
for i in range (0,256) :
if i in hist_key :
num = hist_key.index(i)
hist_countresult.append(hist_new[num])
else :
hist_countresult.append(0)
if len(hist_countresult) < 256 :
for i in range (0,256-len(hist_countresult)) :
hist_new.append(0)
#计算整幅图的像素数目
hist_sum = gray.shape[0] * gray.shape[1]
#计算每个灰度级的像素数目占整个数目的比重
for each in hist_countresult :
result = float(each / hist_sum)
hist_proresult.append(result)
#遍历灰度级[0,255],寻找合适的threshold
w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = deltaMax = float(0)
for i in range (256) :
w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = float(0)
for j in range (256) :
if j <= i : #背景部分
w0 = float(w0 + hist_proresult[j])
u0tmp += j * hist_proresult[j]
else : #前景部分
w1 += float(hist_proresult[j])
u1tmp += j * hist_proresult[j]
if w0 == 0.0 or w1 == 0.0:
pass
else :
u0 = float(u0tmp / w0)
u1 = float(u1tmp / w1)
deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2))
if deltaTmp > deltaMax :
deltaMax = deltaTmp
threshold = i
#用ostu大津算法得出最适当的阈值后,将图片进行二值化
for i in range(gray.shape[0]) :
for j in range(gray.shape[1]) :
#对大于阈值的显示为255白色,小于阈值的显示为0黑色
if gray[i][j] <= threshold :
gray2[i][j] = 0
countdown += 1
else :
gray2[i][j] = 255
countup += 1
return gray2
#分割成为多块区域
def divide(gray,m,n) :
height,width = gray.shape[0],gray.shape[1]
each_width = int(width / (m-1))
each_height = int(height / (n-1))
#生成存放16块图像灰度值的列表
divide_list = []
for i in range (m-1) :
for j in range (n-1):
#生成存放每块小图片灰度级的矩阵
divide_image = np.array([[0 for i in range(each_width)] for i in range(each_height)], dtype=np.uint8)
for divide_i in range (each_height):
for divide_j in range (each_width) :
divide_image[divide_i][divide_j] = gray[ i*each_height + divide_i ][ j*each_width + divide_j]
divide_list.append(divide_image)
return divide_list
#求两个点的差值
def getGrayDiff(image,currentPoint,tmpPoint):
return abs(int(image[currentPoint[0],currentPoint[1]]) - int(image[tmpPoint[0],tmpPoint[1]]))
#区域生长算法
def regional_growth (gray,seeds,threshold=15) :
#每次区域生长的时候的种子像素之间的八个邻接点
connects = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), \
(0, 1), (-1, 1), (-1, 0)]
threshold = threshold #种子生长时候的相似性阈值,默认即灰度级不相差超过15以内的都算为相同
height, weight = gray.shape
seedMark = np.zeros(gray.shape)
seedList = []
for seed in seeds:
seedList.append(seed) #将种子添加到种子的列表中
label = 1 #标记点的flag
while(len(seedList)>0): #如果种子列表里还存在种子点
currentPoint = seedList.pop(0) #将最前面的那个种子抛出
seedMark[currentPoint[0],currentPoint[1]] = label #将对应位置的点标志为1
for i in range(8): #对这个种子点周围的8个点一次进行相似性判断
tmpX = currentPoint[0] + connects[i][0]
tmpY = currentPoint[1] + connects[i][1]
if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= weight: #如果超出限定的阈值范围
continue #跳过并继续
grayDiff = getGrayDiff(gray,currentPoint,(tmpX,tmpY)) #计算此点与种子像素点的灰度级之差
if grayDiff < threshold and seedMark[tmpX,tmpY] == 0:
seedMark[tmpX,tmpY] = label
seedList.append((tmpX,tmpY))
return seedMark
def MAIN():
image = cv2.imread(r"D:/Code/Python/7.png")
cv2.imshow("color_image",image)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
height,width = gray.shape[0],gray.shape[1]
cv2.imshow("gray_img",gray)
#*********************************
# 动态阈值分割
#*********************************
#创建动态阈值矩阵
Dynamic_threshold_image = np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype=np.uint8)
#调用自己写的otsu算法,进行阈值化分割图像处理
m=4
n=4
each_width = int(width / (m))
each_height = int(height / (n))
divide_list = divide(gray,m+1,n+1) #分割成为4*4的块
#用ostu算法计算每一小块图片的最佳阈值,进行动态阈值处理
num = 0 #记录这是第几块图形,第一块为0
for each in divide_list :
#根据第几幅图算出他在原图中所在的行号和列号
r = int (num / m) #行
c = num % n #列
ostu_image = myotsu(each)
for i in range (ostu_image.shape[0]) :
for j in range (ostu_image.shape[1]) :
Dynamic_threshold_image[ i + r*each_height ][ j + c*each_width ] = ostu_image [i][j]
num += 1
cv2.imshow('Dynamic_threshold',Dynamic_threshold_image)
#*********************************
# 全局阈值分割
#*********************************
Global_threshold_image = myotsu(gray)
cv2.imshow('Global_threshold',Global_threshold_image)
#*********************************
# 区域生长分割
#*********************************
seed_points = [(10,150),(100,150),(75,250),(129,210),(263,243)] #输入选取的种子像素
seed_grow_image = regional_growth(gray,seed_points,30)
cv2.imshow('region_growth',seed_grow_image)
cv2.waitKey(0)
if __name__ == "__main__":
MAIN()
实验结果: