(1)传统的图像分割方法
(2)基于深度学习的图像分割方法
(1)分水岭法
(2)GrabCut法
(3)MeanShift法
(4)背景扣除
简单来说,如上图所示。图中有很多的沟壑,当用不同颜色的水灌到沟壑中时,在快灌满时会出现一个边界将两边分开。(每张图片有各种各样的像素,那不同的像素之间就形成沟壑)
(1)标记背景
(2)标记前景
(3)标记未知域
(4)进行分割
(1)watershed(img,masker)
(2)masker:前景,背景设置不同的值用于区分他们(如何获取前景,背景,和未知区域这个需要腐蚀膨胀的相关知识)
(3)前景可以通过腐蚀或距离变换得到,背景可以通过膨胀得到,未知区域可以使用背景减去前景
(4)距离变换API:distanceTransform(img,distanceType,maskSize)其中
distanceType:是测从非0值到0的距离? DIST_L1 DIST_L2??L1求绝对值 L2求勾股定理?; maskSize:就是扫描时kernel的大小 L1用3,L2用5
(7)连通域API:connectedComponents(img,connectivity,……)其中
connectivity:4,8(默认)4代表上下左右四个像素如果是一样的就代表是一个连通域,8在4的基础上加上了4个斜对角
import cv2
import numpy as np
# 获取背景
# 1,通过二值法得到黑白图片
# 2,通过心态学获取背景
img = cv2.imread('water_coins.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# THRESH_BINARY二进制阈值。把亮的处理成白色,暗的处理成黑色,超过100的就变成255,反之变为0。我们使用了_INV那就反过来
ret, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('dst', thresh)
cv2.waitKey(0)
?可以得到图片,发现银币中间还是会出现黑色,但是我们不希望有黑色。
#改进前
ret, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
#改进后
# cv2.THRESH_OTSU可以自适应阈值,将100写为0
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
??可以得到图片,发现银币中间没有黑色了,达到我们的目的。
?全部的代码
import cv2
import numpy as np
# 获取背景
# 1,通过二值法得到黑白图片
# 2,通过心态学获取背景(膨胀)
# 3.获取前景物体
img = cv2.imread('water_coins.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# THRESH_BINARY二进制阈值。把亮的处理成白色,暗的处理成黑色,超过100的就变成255,反之变为0。我们使用了_INV那就反过来
# ret, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
# cv2.THRESH_OTSU可以自适应阈值,将100写为0
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 开运算(去噪)(真的有用!!!)
kernel = np.ones((3, 3), np.int8)
open = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 膨胀
bg = cv2.dilate(open, kernel, iterations=1)
# 获取前景
dist = cv2.distanceTransform(open, cv2.DIST_L2, 5)
ret, fg = cv2.threshold(dist, 0.7 * dist.max(), 255, cv2.THRESH_BINARY)
# 获取未知区域
fg = np.uint8(fg)
unknow = cv2.subtract(bg, fg)
# 创建连通域
ret, marker = cv2.connectedComponents(fg)
marker = marker + 1 # 将所有背景都设为1 前景不变
marker[unknow == 255] = 0
# 进行图像分割
result = cv2.watershed(img, marker) # 结果中边沿都是-1表示
img[result == -1] = [0, 0, 255]
cv2.imshow('img', img)
cv2.imshow('bg', bg)
cv2.imshow('fg', fg)
cv2.imshow('unknow', unknow)
cv2.waitKey(0)
(1)用户指定前景区域,剩下的是背景区域
(2)用户可以明确指出某些地方为前景或背景
(3)GrabCut采用分段迭代的方法分析前景物体形成模型树
(4)最后根据权重决定某个像素是前景还是背景
(1)主体结构(定义接口,成员变量)
(2)鼠标事件的处理
(3)调用GrabCut实现前景和背景的分离
grabCut(img,mask,rect,bgdModel,fgdModel,iterator,mode)
mask:生成的掩码? ?BGD背景0? FGD前景1? PD_BGD可能是背景2? ?PR_FGD可能是前景3
rect: 检测区域
bgdModel: np.float64 type zero arrays of size (1,65)?
fgdModel:?np.float64 type zero arrays of size (1,65)
mode: GC_INIT_WITH_RECT? ? ? GC_INIT_WITH_MASK(第一次使用前一个,第二次使用后面的)
(1)主体结构的搭建
import cv2
import numpy as np
class App:
def onmouse(self, event, x, y, flags, param): # 对于鼠标事件的监听
print("onmouse")
def run(self): # 主体函数
print("run……")
cv2.namedWindow('input')
cv2.setMouseCallback('input', self.onmouse)
img = cv2.imread('lena.png')
cv2.imshow('input', img)
cv2.waitKey(0)
App().run()
(2)添加鼠标事件
def onmouse(self, event, x, y, flags, param): # 对于鼠标事件的监听
# 主要要实现的三个功能(左键的按下,移动和抬起)
if (event == cv2.EVENT_LBUTTONDOWN):
self.flag_ract = True
self.startX = x
self.startY = y
print("LBUTTONDOWN")
elif (event == cv2.EVENT_LBUTTONUP):
self.flag_ract = False
cv2.rectangle(self.img,
(self.startX, self.startY),
(x, y),
(0, 0, 255),
3)
print("LBUTTONUP")
elif (event == cv2.EVENT_MOUSEMOVE):
if self.flag_ract == True:
self.img = self.img2.copy()
cv2.rectangle(self.img,
(self.startX, self.startY),
(x, y),
(255, 0, 0),
3)
print("MOUSEMOVE")
self.img = cv2.imread('lena.png')
self.img2 = self.img.copy()
while (1):
cv2.imshow('input', self.img)
k = cv2.waitKey(100)
if (k == 27):
break
(3)?调用GrabCut实现前景和背景的分离(总)
import cv2
import numpy as np
class App:
flag_ract = False
startX = 0
startY = 0
rect = (0, 0, 0, 0)
def onmouse(self, event, x, y, flags, param): # 对于鼠标事件的监听
# 主要要实现的三个功能(左键的按下,移动和抬起)
if (event == cv2.EVENT_LBUTTONDOWN):
self.flag_ract = True
self.startX = x
self.startY = y
print("LBUTTONDOWN")
elif (event == cv2.EVENT_LBUTTONUP):
self.flag_ract = False
cv2.rectangle(self.img,
(self.startX, self.startY),
(x, y),
(0, 0, 255),
3)
self.rect = (min(self.startX, x), min(self.startY, y),
abs(self.startX - x), abs(self.startY - y))
print("LBUTTONUP")
elif (event == cv2.EVENT_MOUSEMOVE):
if self.flag_ract == True:
self.img = self.img2.copy()
cv2.rectangle(self.img,
(self.startX, self.startY),
(x, y),
(255, 0, 0),
3)
print("MOUSEMOVE")
print("onmouse")
def run(self): # 主体函数
print("run……")
cv2.namedWindow('input')
cv2.setMouseCallback('input', self.onmouse)
self.img = cv2.imread('lena.png')
self.img2 = self.img.copy()
self.mask = np.zeros(self.img.shape[:2], dtype=np.uint8)
self.output = np.zeros(self.img.shape, np.uint8)
while (1):
cv2.imshow('input', self.img)
cv2.imshow('output', self.output)
k = cv2.waitKey(100)
if (k == 27):
break
# 进行分离
if k == ord('g'):
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
cv2.grabCut(self.img2, self.mask, self.rect,
bgdModel, fgdModel,
1,
cv2.GC_INIT_WITH_RECT)
# 分离后需要把截取的全景取出来
# 原始图像与上一个掩码 掩码需要的是最大值ff
mask2 = np.where((self.mask == 1) | (self.mask == 3), 255, 0).astype('uint8')
self.output = cv2.bitwise_and(self.img2, self.img2, mask=mask2)
App().run()
(1)该方法并不是用于图像分割的,而是在彩色层面的平滑滤波
(2)会中和和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域
(3)以图像上让任意一点p为圆心,半径为sp,色彩幅值为sr进行不断的迭代
pyrMeanShiftFiltering(img,sp,sr,maxlevel=1,termcrit=TermCriteria……)
sp:半径
sr:色彩幅值
import cv2
import numpy as np
img = cv2.imread('flower.png')
mean_img = cv2.pyrMeanShiftFiltering(img, sp=20, sr=30)
canny_img = cv2.Canny(mean_img, 150, 300)
contours, hierarchy = cv2.findContours(canny_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (255, 0, 0), 2)
cv2.imshow('img', img)
cv2.imshow('mean_img', mean_img)
cv2.imshow('canny_img', canny_img)
cv2.waitKey(0)
(1)视频是有一组连续的帧组成的
(2)帧与帧之间关系密切(GOP)
(3)在GOP中,背景几乎是不变的
createBacgroundSubtractorMOG( history(默认值200),nmixtures(高斯范围值,默认5),backgroundRatio(背景比率,默认为0.7),noiseSigma(默认0,自动降噪)
import cv2
import numpy as np
cap = cv2.VideoCapture('vtest.avi')
mog = cv2.bgsegm.createBackgroundSubtractorMOG()
while (True):
ret, frame = cap.read()
fgmask = mog.apply(frame)
cv2.imshow('img', fgmask)
k = cv2.waitKey(30)
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
优点:同MOG类似,不过对亮度产生的阴影有更好的识别
缺点:前后景分离时会产生很多噪点
createBacgroundSubtractorMOG2( history(默认值500),……detecShadow(是否检测阴影,True))
import cv2
import numpy as np
cap = cv2.VideoCapture('vtest.avi')
# 好处,可以计算出阴影部分
# 缺点,会产生很多噪点
mog = cv2.createBackgroundSubtractorMOG2()
while (True):
ret, frame = cap.read()
fgmask = mog.apply(frame)
cv2.imshow('img', fgmask)
k = cv2.waitKey(30)
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
优点:静态背景图像估计和每个像素的贝叶斯分割抗噪性更强
缺点:默认值帧数很大,需要等待较长时间
createBacgroundSubtractorGMG(initializaFranmes(初始帧数 120))
import cv2
import numpy as np
cap = cv2.VideoCapture('vtest.avi')
# 好处,可以算出阴影部分,同时减少噪点
# 缺点,如果采用默认值,开始很长时间没有任何显示
# 解决方法,调整初始参考帧数量
mog = cv2.bgsegm.createBackgroundSubtractorGMG(10) # 这个就是参考帧数量
while (True):
ret, frame = cap.read()
fgmask = mog.apply(frame)
cv2.imshow('img', fgmask)
k = cv2.waitKey(30)
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
?
inpaint(img,mask,inpaintRadius,flags)
img:原始图像
mask:与原始图像大小相同的黑底白色的残缺位置的图片(可以自己制作出来)
inpaintRadius:每个破损的点会画一个半径,根据半径内的值计算出破损区域的值并填入
flags:INPAINT_NS , INPAINT_TELEA
import cv2
import numpy as np
img = cv2.imread('inpaint.png')
mask = cv2.imread('inpaint_mask.png', 0) # 0 使其变成8位
dst = cv2.inpaint(img, mask, 5, cv2.INPAINT_TELEA)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
原图处理后的图