OpenCV_13 图像分割与修复

前言

本节主要介绍图像分割和修复的方法和OpenCV中提供的算法,并提供代码例程。


一、

1.什么是图像分割?

   将前景物体从背景中分离出来

2.图像分割方法

  • 传统的图像分割方法
    (1)分水岭法
    (2)GrabCut法
    (3)MeanShift
    (4)背景扣除
  • 深度学习的图像分割

二、传统图像分割方法

1.分水岭法

原理

在这里插入图片描述
   分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法.分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。

   分水岭的计算过程是一个迭代标注过程。比较经典的计算方法是L. Vincent提出的。在该算法中,分水岭计算分两个步骤,一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高排序,然后在从低到高实现淹没过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。

   分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时应当看出,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证的。另外,分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。

在这里插入图片描述

   当图像存在过多的极小区域而产生的许多小的集水盆(红色区域),但实际我们可能只需要那些大的集水盆(绿色区域)。因此分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时应当看出,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证的。另外,分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。

步骤

  • 标记背景
  • 标记前景
  • 标记未知区域
    前景、背景、未知区域形成掩码后,当做参数传递给OpenCV的分水岭算法
  • 进行分割

watershed() 分水岭算法API
声明:void watershed( InputArray image, InputOutputArray markers );
参数:
   image:需要分割的图像
   markers:前景、背景、未知区域设置不同的值用以区分它们

distanceTransform() 距离变换API,计算非零像素到零的距离
声明:void distanceTransform( InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType = DIST_LABEL_CCOMP );
参数:
   distanceType:计算距离的函数
      DIST_L1:计算长宽绝对值的和
      DIST_L2:勾股定理计算距离
   maskSize:扫描时kernel的大小
      L1用3、L2用5

connectedComponents() 求连通域API
声明:int connectedComponents(InputArray image, OutputArray labels, int connectivity, int ltype, int ccltype);
参数:
   connectivity:计算连通域方法:4,8(默认)周围4或8个像素点
返回值:
   返回一个int整型 nccomps,函数返回值为连通区域的总数N,范围为[0,N-1],其中0代表背景。

代码示例:

import cv2
import numpy as np 
from matplotlib import pyplot as plt

# 获取背景
# 1.通过二值法得到黑白图片
# 2.通过形态学获取背景

img = cv2.imread('./picture/water_coins.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 开运算去噪点
kernel = np.ones((3, 3), np.int8)
open1 = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations = 2)
# 膨胀
bg = cv2.dilate(open1, kernel, iterations = 1)

# 获取前景物体
dist = cv2.distanceTransform(open1, cv2.DIST_L2, 5)

ret, fg = cv2.threshold(dist, 0.7*dist.max(), 255, cv2.THRESH_BINARY)

# plt.imshow(dist, cmap = 'gray')
# plt.show()
# exit()

# 获取未知区域
fg = np.uint8(fg)
unknow = cv2.subtract(bg, fg)

# 创建连通域
ret, marker = cv2.connectedComponents(fg)
marker = marker + 1
marker[unknow == 255] = 0

# 进行图像分割
result = cv2.watershed(img, marker)
img[result == -1] = [0, 0, 255]

cv2.imshow("bin", thresh)
cv2.imshow("bg", bg)
cv2.imshow("fg", fg)
cv2.imshow("unknow", unknow)
cv2.imshow("img", img)
cv2.waitKey(0)

结果展示:
请添加图片描述

2.GrabCut

   通过交互的方式获得前景物体,例如:框选出物体的大致范围,然后进行图像分割,在各种图片处理软件中都有运用。

grabCut()
声明:void grabCut( InputArray img, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, InputOutputArray fgdModel, int iterCount, int mode = GC_EVAL );
参数:
   mask:分割后的掩码,可通过其将图像抠出
   rect:选中区域
   bgdModel和fgdModel:固定值
   iterCount:grabCut迭代的次数
   mode:模式

mask:
BGD:背景,0
FGD:前景,1
PR_BGD:可能是背景,2
PR_FGD:可能是前景,3

mode:
GC_INIT_WITH_RECT:在指定矩形区域搜索前后景
GC_INIT_WITH_MASK:第一次用rect,以后通过mask提取前后景

代码案例:

import cv2
import numpy as np 

class App:
    flag_rect = 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_rect = True
            self.startX = x
            self.startY = y
            print("LBUTTONDOWN")
        elif event == cv2.EVENT_LBUTTONUP:
            self.flag_rect = False
            cv2.rectangle(self.img, (self.startX, self.startY), (x, y), (0, 0, 255), 2)
            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_rect == True:
                self.img = self.img2.copy()
                cv2.rectangle(self.img, (self.startX, self.startY), (x, y), (255, 0, 0), 2)
            print("MOUSEMOVE")

        print("onmouse")
    

    def run(self):
        print("run")

        cv2.namedWindow("input")
        cv2.setMouseCallback("input", self.onmouse)

        self.img = cv2.imread("./picture/lana.jpeg")
        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)

            key = cv2.waitKey(100)
            if key == 27:
                break
            if key == 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)
            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()

结果展示:
请添加图片描述

3.MeanShift

   严格来说MeanShift并不是用来对图像分割的,而是在色彩层面的平滑滤波,它会中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域。它以图像上任意一点P为圆心,半径为sp,色彩幅值为sr进行不断的迭代。

pyrMeanShiftFiltering()
声明:void pyrMeanShiftFiltering( InputArray src, OutputArray dst, double sp, double sr, int maxLevel = 1, TermCriteria termcrit=TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) );
参数:
   sp:半径
   sr:色彩幅值的变化范围

代码示例:

# 平滑色彩
mean_img = cv2.pyrMeanShiftFiltering(img, 20, 30)
# 提取边缘
canny_img = cv2.Canny(mean_img, 150, 300)
contours, _ = cv2.findContours(canny_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制边沿
cv2.drawContours(img, contours, -1, (0, 0, 255), 2)

三、视频背景抠除

   视频实际上是一组连续的帧,帧与帧之间关系密切(GOD),在GOD中,背景几乎是不变的。

1.MOG去背景

   混合高斯模型为基础的前景/背景分割算法

createBackgroundSubtractorMOG2()
声明:createBackgroundSubtractorMOG2(int history=500, double varThreshold=16, bool detectShadows=true);
参数:
   history:读取的历史帧,单位毫秒
   detectShadows:是否检测阴影

代码示例:

import cv2
import numpy as np 

cap = cv2.VideoCapture('./video/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()

结果展示:
请添加图片描述

2.GMG去背景

   从上图可以看出MOG2去背景方法会产生大量的噪点,由此提出GMG方法。静态背景图像估计和每个像素的贝叶斯分割抗噪性更强

3.图像修复

inpaint()
声明:void inpaint( InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags );
参数:
   inpaintMask:与原始图像尺寸一样,黑底白色残缺位置的一张图片
   inpaintRadius:算法考虑的每个修复点的圆形邻域半径
   flags:
      INPAINT_NS
      INPAINT_TELEA

使用inpaint进行图像修复需要一张黑色背景,白色图案的原图像的缺失部分,且必须是单通道的。可以通过图像分割的方法获得。

代码示例:

import cv2
import numpy as np 

img = cv2.imread("./picture/inpaint.png")
mask = cv2.imread("./picture/inpaint_mask.png")
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)

dst = cv2.inpaint(img, mask, 5, cv2.INPAINT_TELEA)

cv2.imshow("img", img)
cv2.imshow("dst", dst)
cv2.waitKey()

结果展示:

请添加图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值