图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出感兴趣目标的技术和过程。它是由图像处理到图像分析的关键步骤。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。它在机器视觉、人脸识别、指纹识别等多个领域都发挥着极为重要的作用。下面,我们来简单了解一下图像分割的几种方法。
阈值分割:将所有灰度值大于或等于某阈值的像素都被判属于物体,将所有灰度值小于该阈值的像素被安排在物体之外,适用于物体与背景有较强对比的景物的分割。
由于阈值分割的直观性和易于实现的性质,使它在图像分割应用中处于中心地位。
在阈值分割中,阈值的选择将直接太影响分割效果,通常可以通过对灰度直方图的分析来确定它的值。当图像的直方图为双峰或多峰时,可选择两峰之间的谷底作为阈值。
1、人工选择
2、全局阈值
如果背景的灰度值在整个图像中可合理的看作为恒定,而且所有物体与背景都具有几乎相同的对比度,那么,只要选择了正确的阈值,使用一个固定的全局阈值一般会有较好的结果。
3、自适应阈值
在许多的情况下背景的灰度值并不是恒定的,这时,一个在图像某区域效果良好的阈值在其他区域却可能效果很差。在这种情况下,把灰度阈值取成一个随图像中位置缓慢变化的函数值时适宜的。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img=cv2.imread(d:/imdata/coins.png',0) #0是第二个参数,将其转为灰度图
img = cv2.medianBlur(img,5)
#全局阈值
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
#自适应阈值
th2=cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,11,2)#按均值进行阈值选择
titles = ['Original', 'Global Threshold', 'Adaptive threshold']
images = [img, th1, th2, th3]
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
最佳阈值的选择:直方图技术、最大类间方差法、迭代法求阈值。
• 直方图技术:适用于图像直方图中有双峰或多峰的情况。其步骤为:
1、选择谷底
2、把阈值设在相对于两峰的某个固定位置。
• 最大类间法:又称为OTSU算法,该算法是在灰度直方图的基础上用最小二乘法原理推导出来的,具有统计意义上的最佳分割阈值。基本原理是以最佳阈值将图像的灰度直方图分割成两部分,使两部分之间的方差取最大值,及分离性最大。
• 迭代法
1)选择图像灰度值的中值作为初始阈值Ti=T0
2)利用阈值Ti将图像分割成两部分区域,R1和R2,并计算其灰度均值。
3)计算新的阈值Ti+1,Ti+1=1/2(u1+u2)
4)重复步骤2,3,直到小于某个给定值。
阈值分割由于没有或很少考虑空间关系,使得阈值分割方法受到限制,这时候,区域分割法出现了。基于区域的分割方法可以弥补这方面的不足,它利用的是图像的空间性质,该方法认为分割出来的属于同一区域的像素应既有相似的性质,其概念是相当直观的。
传统的区域分割算法又区域生长法和分裂合并法两种。
区域生长是一种根据实现定义的准则将像素子区域聚合成更大区域的过程。
区域生长法主要考虑像素及其空间邻域像素的关系,其具体的步骤为:
1)选定一个或多个像素点作为种子。
2)按照某种相似性准则增长区域,逐步生成具有某种均匀性的空间区域,将相邻的具有相似性质的像素或区域归并从而逐步增长区域,直至没有可以归并的点或其他小区域为止。
• 区域内像素的相似性度量可以包括平均灰度值、纹理、颜色等信息。
• 生长准则:所考虑的像素点的灰度值和种子点区域的平均灰度值的差的绝对值小于或等于某个阈值T,就将该像素点归入种子点所在的区域。
区域分割法里比较重要的过程有:
1)选择合适的种子点
2)确定相似性准则(生长准则)
3)确定生长停止条件
import cv2
import numpy as np
import matplotlib.pyplot as plt
#初始种子选择
def originalSeed(gray, th):
ret, thresh = cv2.cv2.threshold(gray, th, 255, cv2.THRESH_BINARY)#二值图,种子区域(不同划分可获得不同种子)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))#3×3结构元
thresh_copy = thresh.copy() #复制thresh_A到thresh_copy
thresh_B = np.zeros(gray.shape, np.uint8) #thresh_B大小与A相同,像素值为0
seeds = [ ] #为了记录种子坐标
#循环,直到thresh_copy中的像素值全部为0
while thresh_copy.any():
Xa_copy, Ya_copy = np.where(thresh_copy > 0) #thresh_A_copy中值为255的像素的坐标
thresh_B[Xa_copy[0], Ya_copy[0]] = 255 #选取第一个点,并将thresh_B中对应像素值改为255
#连通分量算法,先对thresh_B进行膨胀,再和thresh执行and操作(取交集)
for i in range(200):
dilation_B = cv2.dilate(thresh_B, kernel, iterations=1)
thresh_B = cv2.bitwise_and(thresh, dilation_B)
#取thresh_B值为255的像素坐标,并将thresh_copy中对应坐标像素值变为0
Xb, Yb = np.where(thresh_B > 0)
thresh_copy[Xb, Yb] = 0
#循环,在thresh_B中只有一个像素点时停止
while str(thresh_B.tolist()).count("255") > 1:
thresh_B = cv2.erode(thresh_B, kernel, iterations=1) #腐蚀操作
X_seed, Y_seed = np.where(thresh_B > 0) #取处种子坐标
if X_seed.size > 0 and Y_seed.size > 0:
seeds.append((X_seed[0], Y_seed[0]))#将种子坐标写入seeds
thresh_B[Xb, Yb] = 0 #将thresh_B像素值置零
return seeds
def regionGrow(gray, seeds, thresh, p):
seedMark = np.zeros(gray.shape)
#八邻域
if p == 8:
connection = [(-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1)]
elif p == 4:
connection = [(-1, 0), (0, 1), (1, 0), (0, -1)]
#seeds内无元素时候生长停止
while len(seeds) != 0:
#栈顶元素出栈
pt = seeds.pop(0)
for i in range(p):
tmpX = pt[0] + connection[i][0]
tmpY = pt[1] + connection[i][1]
#检测边界点
if tmpX < 0 or tmpY < 0 or tmpX >= gray.shape[0] or tmpY >= gray.shape[1]:
continue
if abs(int(gray[tmpX, tmpY]) - int(gray[pt])) < thresh and seedMark[tmpX, tmpY] == 0:
seedMark[tmpX, tmpY] = 255
seeds.append((tmpX, tmpY))
return seedMark
img = cv2.imread('d:/imdata/coins.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
seeds = originalSeed(gray, th=53)
seedMark = regionGrow(gray, seeds, thresh=3, p=8)
cv2.imshow("seedMark", seedMark)
cv2.waitKey(0)
可以看出,通过区域生长法分割后的图像,每一区域的差异十分明显。
区域分裂合并算法的基本思想是先确定一个分裂合并的准则,当图像中某个区域的特征不一致时就将该区域分裂成4 个相等的子区域,当相邻的子区域满足一致性特征时则将它们合成一个大区域,直至所有区域不再满足分裂合并的条件为止。
import numpy as np
import cv2
def judge(w0, h0, w, h):
a = img[h0: h0 + h, w0: w0 + w]
ave = np.mean(a)
std = np.std(a, ddof=1)
count = 0
total = 0
for i in range(w0, w0 + w):
for j in range(h0, h0 + h):
if abs(img[j, i] - ave) < 1 * std:
count += 1
total += 1
if (count / total) < 0.95:
return True
else:
return False
def draw(w0, h0, w, h):
for i in range(w0, w0 + w):
for j in range(h0, h0 + h):
if img[j, i] > 175:
img[j, i] = 255
else:
img[j, i] = 0
def function(w0, h0, w, h):
if judge(w0, h0, w, h) and (min(w, h) > 5):
function(w0, h0, int(w / 2), int(h / 2))
function(w0 + int(w / 2), h0, int(w / 2), int(h / 2))
function(w0, h0 + int(h / 2), int(w / 2), int(h / 2))
function(w0 + int(w / 2), h0 + int(h / 2), int(w / 2), int(h / 2))
else:
draw(w0, h0, w, h)
img = cv2.imread('d:/imdata/coins.png', 0)
img_input = cv2.imread('d:/imdata/coins.png', 0)
height, width = img.shape
function(0, 0, width, height)
cv2.imshow('input',img_input)
cv2.imshow('output',img)
cv2.waitKey()
cv2.destroyAllWindows()
本章我们学习了图像分割的一些相关内容,了解了一些阈值分割和区域分割方法并进行了实验。图像分割是数字图像处理中极为重要的一部分,是我们以后学习目标识别内容所必须的基础知识。