• 【OpenCV图像处理14】图像分割与修复


    十四、图像分割与修复

    1、图像分割

    图像分割: 将前景物体从背景中分离出来。

    图像分割的方法:

    • 传统的图像分割方法
      • 分水岭法
      • GrabCut法
      • MeanShift法
      • 背景抠图
    • 基于深度学习的图像分割方法

    1.1 分水岭

    1、分水岭法的原理:

    2、分水岭法的问题:

    图像存在过多的极小区域,从而产生许多小的集水盆。

    3、分水岭法的基本步骤:

    • 标记背景
    • 标记前景
    • 标记未知域
    • 进行分割

    4、实战:分割硬币

    watershed()用法:

    cv2.watershed(image, markers)
    
    • 1

    参数说明:

    • markers:前景、背景设置不同的值用以区分它们

    原图像:

    获取背景:

    获取前景:

    获取未知域:

    4.1 获取背景:

    # 获取背景
    # 1.通过二值法得到黑白图像
    # 2.通过形态学获取背景
    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)
    
    cv2.imshow('thresh', thresh)
    cv2.imshow('bg', bg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.2 获取前景:

    距离变换:distanceTransform()用法

    cv2.distanceTransform(src, distanceType, maskSize, dst: None, dstType: None)
    
    • 1

    参数说明:

    • distanceType:DIST_L1(绝对值),DIST_L2(勾股定理)
    • maskSize:L1:3,L2:5
    # 获取前景
    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()
    
    cv2.imshow('dist', dist)
    cv2.imshow('fg', fg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用Matplotlib画出dist:

    4.3 获取未知域:

    求连通域:connectedComponents()用法

    cv2.connectedComponents(image, labels: None, connectivity: None, ltype: None)
    
    • 1

    参数说明:

    • connectivity:4,8(默认)
    # 获取未知域
    fg = np.uint8(fg)
    unknown = cv2.subtract(bg, fg)
    
    # 创建连通域
    ret, marker = cv2.connectedComponents(fg)
    
    cv2.imshow('unknown', unknown)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.4 进行分割:

    # 进行分割
    result = cv2.watershed(img, marker)
    img[result == -1] = [0, 0, 255]
    
    cv2.imshow('img', img)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码实现(完整代码):

    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    
    img = cv2.imread('../resource/money.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 获取背景
    # 1.通过二值法得到黑白图像
    # 2.通过形态学获取背景
    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)
    unknown = cv2.subtract(bg, fg)
    
    # 创建连通域
    ret, marker = cv2.connectedComponents(fg)
    
    marker += 1
    marker[unknown == 255] = 0
    
    # 进行分割
    result = cv2.watershed(img, marker)
    img[result == -1] = [0, 0, 255]
    
    # cv2.imshow('thresh', thresh)
    # cv2.imshow('bg', bg)
    # cv2.imshow('dist', dist)
    # cv2.imshow('fg', fg)
    # cv2.imshow('unknown', unknown)
    cv2.imshow('img', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 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

    1.2 GrabCut

    GrabCut:通过交互的方式获得前景物体。

    基本原理:

    (1):用户可以指定前景的大体区域,剩下的为背景区域。

    (2):用户还可以明确指定某些地方为前景或背景。

    (3):GrabCut采用分段迭代的方法分析前景物体形成模型树。

    (3):最后根据权重决定某个像素是前景还是背景。

    实战步骤:

    (1):主体结构

    (2):鼠标事件的处理

    (3):调用GrabCut实现图像分割

    1、主体结构:

    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('../resource/lena.bmp')
            cv2.imshow('input', img)
            cv2.waitKey(0)
            cv2.destroyAllWindows()
    
    App().run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2、鼠标事件的处理

    class App:
        flag_rect = False
        startX = 0
        startY = 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), 3)
                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), (0, 255, 0), 3)
                print('MOUSEMOVE: 鼠标移动')
            print('onmouse')
    
        def run(self):
            print('run')
    
            cv2.namedWindow('input')
            cv2.setMouseCallback('input', self.onmouse)
    
            self.img = cv2.imread('../resource/lena.bmp')
            self.img2 = self.img.copy()
    
            while (1):
                cv2.imshow('input', self.img)
                key = cv2.waitKey(100)
                if key == 27:
                    break
    
    App().run()
    
    • 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

    3、调用GrabCut实现图像分割

    grabCut()用法:

    cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode: None)
    
    • 1

    参数说明:

    • mask:生成的掩码
      • BGD:背景,0
      • FGD:前景,1
      • PR_BGD:可能是背景,2
      • PR_FGD:可能是前景,3
    • bgdModel, fgdModel:np.float64 type zero arrays of size(1, 65)
    • mode:模式
      • GC_INIT_WITH_RECT:指定某个区域,即在该区域中找前景
      • GC_INIT_WITH_MASK:如果是第二次或第三次,可使用该参数再次迭代

    代码实现(完整代码):

    import cv2
    import numpy as np
    
    class App:
        flag_rect = False
        rect = (0, 0, 0, 0)
        startX = 0
        startY = 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), 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_rect == True:
                    self.img = self.img2.copy()
                    cv2.rectangle(self.img, (self.startX, self.startY), (x, y), (0, 255, 0), 3)
                print('MOUSEMOVE: 鼠标移动')
            print('onmouse')
    
        def run(self):
            print('run')
    
            cv2.namedWindow('input')
            cv2.setMouseCallback('input', self.onmouse)
    
            self.img = cv2.imread('../resource/lena.bmp')
            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()
    
    • 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

    1.3 MeanShift法

    严格来说,该方法并不是用来对图像分割的,而是在色彩层面的平滑滤波。

    它会中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域。以图像上任一点p为圆心,半径为sp,色彩幅值为sr进行不断的迭代。

    pyrMeanShiftFiltering()用法:

    cv2.pyrMeanShiftFiltering(src, sp, sr, dst: None, maxLevel: None, termcrit: None)
    
    • 1

    参数说明:

    • sp:圆的半径
    • sp:色彩幅值

    代码实现:

    import cv2
    
    img = cv2.imread('../resource/flower.png')
    
    img_mean = cv2.pyrMeanShiftFiltering(img, 20, 30)
    img_canny = cv2.Canny(img_mean, 150, 300)
    
    contours, _ = cv2.findContours(img_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(img, contours, -1, (0, 0, 255), 2)
    
    cv2.imshow('img', img)
    cv2.imshow('img_mean', img_mean)
    cv2.imshow('img_canny', img_canny)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2、视频前后景分离(视频背景抠图)

    原视频:

    原理:

    • 视频是一组连续的帧(一幅幅图组成)
    • 帧与帧之间关系密切(GOP)
    • 在GOP中,背景几乎是不变的

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

    createBackgroundSubtractorMOG()用法:

    cv2.bgsegm.createBackgroundSubtractorMOG(history: None, nmixtures: None, backgroundRatio: None, noiseSigma: None)
    
    • 1

    参数说明:

    • history:参考帧,默认 200
    • nmixtures:高斯范围值,默认 5
    • backgroundRatio:背景比率,默认 0.7
    • noiseSigma:自动降噪,默认 0

    代码实现:

    import cv2
    
    cap = cv2.VideoCapture('../resource/Car.mp4')
    
    mog = cv2.bgsegm.createBackgroundSubtractorMOG()
    
    while (True):
        ret, frame = cap.read()
        fgmask = mog.apply(frame)
    
        cv2.imshow('img', fgmask)
    
        key = cv2.waitKey(10)
        if key == 27:
            break
    
    cap.release()
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.1 MOG2去背景

    同MOG类似,不过对亮度产生的阴影有更好的识别.

    createBackgroundSubtractorMOG2()用法:

    cv2.createBackgroundSubtractorMOG2(history: None, varThreshold: None, detectShadows: None)
    
    • 1

    参数说明:

    • history:参数帧,默认 500
    • detectShadows:是否检测阴影,默认 True

    代码实现:

    import cv2
    
    cap = cv2.VideoCapture('../resource/Car.mp4')
    
    # 优点: 可以计算出阴影部分
    # 缺点: 会产生很多噪点
    mog2 = cv2.createBackgroundSubtractorMOG2()
    
    while (True):
        ret, frame = cap.read()
        fgmask = mog2.apply(frame)
    
        cv2.imshow('img', fgmask)
    
        key = cv2.waitKey(10)
        if key == 27:
            break
    
    cap.release()
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.2 GMG去背景

    静态背景图像估计和每个像素的贝叶斯分割,抗噪性更强。

    createBackgroundSubtractorGMG()用法:

    cv2.bgsegm.createBackgroundSubtractorGMG(initializationFrames: None, decisionThreshold: None)
    
    • 1

    参数说明:

    • initializationFrames:初始帧数,默认 120

    代码实现:

    import cv2
    
    cap = cv2.VideoCapture('../resource/Car.mp4')
    
    # 优点: 可以算出阴影部分,同时减少了噪点
    # 缺点: 如果采用默认值,则在开始会有很长时间不显示
    gmg = cv2.bgsegm.createBackgroundSubtractorGMG(initializationFrames=10)
    # 解决办法: 调整初始参考帧的数量
    
    while (True):
        ret, frame = cap.read()
        fgmask = gmg.apply(frame)
    
        cv2.imshow('img', fgmask)
    
        key = cv2.waitKey(10)
        if key == 27:
            break
    
    cap.release()
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3、图像修复

    图像修复效果:

    inpaint()用法:

    cv2.inpaint(src, inpaintMask, inpaintRadius, flags, dst: None)
    
    • 1

    参数说明:

    • inpaintMask:修复掩码
    • inpaintRadius:每个点的圆形领域半径
    • flags:
      • INPAINT_NS
      • INPAINT_TELEA
    • dst:输出与src具有相同大小和类型的图像

    代码实现:

    import cv2
    import numpy as np
    
    img = cv2.imread('../resource/cvLogo_Ori.png')
    mask = cv2.imread('../resource/cvLogo_Mask.png', 0)
    
    dst = cv2.inpaint(img, mask, 5, cv2.INPAINT_TELEA)
    
    cv2.imshow('img', np.hstack((img, dst)))
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

  • 相关阅读:
    2022最新 wifi大师小程序独立版3.0.8
    conan 基本配置
    什么是 WebRTC?
    Linux 学习笔记 2022-11-12---------Linux基础
    Android存储权限完美适配(Android11及以上适配)
    pytorch中torch.mul、torch.mm、torch.matmul的区别
    『现学现忘』Docker基础 — 28、Docker容器数据卷介绍
    没有公网ip怎么做外网访问内网端口?快解析内网穿透
    vue页面报Expected indentation of 2 spaces but found 4.eslintindent
    java给图片添加自定义文字信息
  • 原文地址:https://blog.csdn.net/m0_70885101/article/details/126720660