OpenCV Segmentation 区域分割

阈值分割

一个或几个灰度阈值将图像的中的区域分成不同的类

一般适用于只需要将图像分割为两个区域的情况(当然可以分割为多个区域,但是此方法的算法较为简单,一般只用于二分)

最常用的方法是OTSU大津阈值分割算法

边缘分割

即先提取图像边缘,将边缘进行连接后,划分为不同的区域

Snakes模型

常用的是Snakes模型,用一条可变形的参数曲线及相应的能量函数,以最小化能量函数为目标,控制参数曲线变形,具有最小能量的闭合曲线就是目标轮廓

还可以使用点连接关系判断区域,即建立图像的邻接矩阵,判断两点是否属于同一区域

此方法较难,本实验对其进行了调研和实验,没有获得可接受的效果,故放弃

GrabCut算法

GrabCut是微软设计事项的一种基于边缘的图像分割算法.使用时在前景物体外画框选中,算法会讲框外理解为背景,从而将框内的背景区域进行识别,选中前景物体

区域分割

最常用Watersheds分水岭算法,这种算法将图像灰度值的大小理解为地貌的高度

分水岭分割的过程可以大致上这么理解,在全图范围内降水,盆地部分慢慢被填满,当不同集水盆将要相互连通是,筑起水坝,水坝也就是分水线。当所有的区域都被水淹过后,分水岭也就都建完成了

程序代码及注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#! /usr/local/bin/python
# -*- coding: UTF-8 -*-
import cv2
import numpy as np
img=cv2.imread("yujinxiang.png",1)# flags>0,以BGR格式读入,忽略透明度的channel
cv2.imshow("Original",img)# 显示图像
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 转换为灰度图像
rows,cols,channels=img.shape# 获取图像尺寸及通道(BGR三通道)
blurred = cv2.GaussianBlur(gray, (9, 9),0)# kernel-size-(9,9) sigmaX-0
################GrabCut算法,实验效果较差,舍弃##################
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel=np.zeros((1,65),np.float64)
fgdModel=np.zeros((1,65),np.float64)
rect=(550,200,650,350)#(250,100,900,500)#(550,200,650,350)#框
#cv2.rectangle(img,(550,200),(650,350),(100,100,255),2)#cv::rectangle (InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
cv2.imshow("GrabCut Region",img)

cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT) #5-高斯模型类别
mask2=np.where((mask==2)|(mask==0),0,1).astype('uint8')#划分前景/背景
grab_img=img*mask2[:,:,np.newaxis]
cv2.imshow("GrabCut Image",grab_img)# 显示图像
#########动态椭圆寻找轮廓,较难,不能很好理解,且不适合本作业图像,仅实验##########
''' Sobel
索比尔算子来计算x、y方向梯度

gradX = cv2.Sobel(blurred, ddepth=cv2.CV_32F, dx=1, dy=0)
gradY = cv2.Sobel(blurred, ddepth=cv2.CV_32F, dx=0, dy=1)
gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)
'''

''' threshold
cv2.threshold(src,thresh,maxval,type[,dst])->retval,dst (二元值的灰度图)
src: 一般输入灰度图
thresh:阈值,
maxval:在二元阈值THRESH_BINARY和
逆二元阈值THRESH_BINARY_INV中使用的最大值
type: 使用的阈值类型
返回值 retval其实就是阈值

blurred = cv2.GaussianBlur(gradient, (9, 9),0)
(_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)

# 动态椭圆-包围轮廓
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
closed = cv2.erode(closed, None, iterations=4)
closed = cv2.dilate(closed, None, iterations=4)
(cnts, _) = cv2.findContours(closed.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
c = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
# 计算最大轮廓的旋转包围盒
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))

draw_img = cv2.drawContours(img.copy(), [box], -1, (0, 0, 255), 3)

Xs = [i[0] for i in box]
Ys = [i[1] for i in box]
x1 = min(Xs)
x2 = max(Xs)
y1 = min(Ys)
y2 = max(Ys)
hight = y2 - y1
width = x2 - x1
crop_img = img[y1:y1+hight, x1:x1+width]

cv2.imshow('draw_img', draw_img)
cv2.imshow('crop_img', crop_img)
'''
###############基于颜色,使用OTSU&Watersheds算法区域划分#################
# 使用OTSU threshold,高于阈值的被赋值255
# 考虑到红色花朵与绿色背景的颜色差异较大,可以直接通过颜色的判断将花朵区域大致进行分割,所以采用设定花朵颜色范围,粗略提取花朵区域的方法
# 首先试用直接用R分量进行分割,效果较差,舍弃
# (B,G,R) = cv2.split(img)#提取R、G、B分量
# 于是设定了花朵的颜色范围 color
color = [
([30, 50, 200], [150, 180, 255])#[B,G,R]
]
for (lower, upper) in color:
# 创建NumPy数组
lower = np.array(lower, dtype = "uint8")#颜色下限
upper = np.array(upper, dtype = "uint8")#颜色上限
# 根据阈值找到对应颜色
mask = cv2.inRange(img, lower, upper)
# output = cv2.bitwise_and(img, image, mask = mask)
cv2.imshow('Mask image', mask)

ret, thresh = cv2.threshold(mask,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
cv2.imshow('OTSU image', thresh)
# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2) # 形态开运算
# 对图像进行扩张,剩余部分可以较为确定为
sure_bg = cv2.dilate(opening,kernel,iterations=3)
# 对图像进行腐蚀,即中心区域确定为所需的花朵区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# 剩余区域为未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers+1
# Now, mark the region of unknown with zero
markers[unknown==255] = 0
# 使用分水岭算法绘制区域边缘
markers = cv2.watershed(img,markers)
img[markers == -1] = [0,0,255]
cv2.imshow('OTSU Image',img)
cv2.waitKey(0) #关闭窗口/键盘ESC退出
cv2.destroyAllWindows()

执行结果

原始图像:

GrabCut画框区域:

GrabCut效果:

基于颜色的OTSU&Watersheds方法中使用的mask:

OTSU&Watersheds方法效果:

可以看到,使用两种方法得到的效果都不是很好.本次实验我不理解的地方较多,请批评指正🙏

0%