OpenCV GrabCut 图像分割

打印 上一主题 下一主题

主题 992|帖子 992|积分 2978

1. GrabCut 算法简介
GrabCut 是一种基于图割(Graph Cut)的交互式图像分割算法。它只需要用户提供少量的前景和配景信息(通常是一个包罗前景的矩形框),就能通过迭代优化,实现高质量的前景提取。
核心思想:


  • 构建图模子: 将图像像素视为图的节点,相邻像素之间通过边连接。边的权重反映了像素之间的相似度(颜色、梯度等)。
  • 用户交互: 用户通过矩形框指定一个包罗前景的区域。矩形框外的区域被认为是确定的配景(GC_BGD),框内的区域被认为是可能的前景(GC_PR_FGD)。
  • 高斯混淆模子(GMM): 分别为前景和配景构建颜色分布的高斯混淆模子。每个像素根据其颜色属于前景或配景的概率被标记。
  • 迭代优化: 通过最小化能量函数(思量像素颜色与 GMM 的匹配度以及相邻像素标签的平滑性),不断更新像素的标签(前景/配景)和 GMM 参数,直到收敛。
2. 代码逐步解析
我们来详细分析你提供的 Python 代码,理解每一步的作用。
  1. import cv2
  2. import numpy as np
  3. # 定义一个应用类,用于图像处理和鼠标交互
  4. class app:
  5.     # 初始化类变量,用于标记是否正在绘制矩形,以及矩形的相关信息
  6.     flag_rect=False
  7.     rect=(0,0,0,0)
  8.     startx= 0
  9.     starty= 0
  10.    
  11.     # 鼠标回调函数,处理鼠标事件
  12.     def onmouse(self, event, x, y, flags, param):
  13.         # 当鼠标左键按下时,开始记录起始位置
  14.         if event == cv2.EVENT_LBUTTONDOWN:
  15.             self.flag_rect= True
  16.             self.startx= x
  17.             self.starty= y
  18.             print("down")
  19.         # 当鼠标左键抬起时,绘制矩形并记录矩形信息
  20.         elif event == cv2.EVENT_LBUTTONUP:
  21.             self.img2=self.img.copy()
  22.             self.flag_rect= False
  23.             cv2.rectangle(self.img2, (self.startx, self.starty), (x, y), (0, 0, 255), 2)
  24.             self.rect=(min(self.startx,x), min(self.starty,y), abs(self.startx-x), abs(self.starty-y))
  25.             print("up")
  26.         # 当鼠标移动且左键按下时,动态绘制矩形
  27.         elif event == cv2.EVENT_MOUSEMOVE and (self.flag_rect & cv2.EVENT_FLAG_LBUTTON):
  28.             self.img2=self.img.copy()
  29.             cv2.rectangle(self.img2, (self.startx, self.starty), (x, y), (0, 255, 0), 2)
  30.             print("move")
  31.    
  32.     # 主程序运行函数
  33.     def run(self):
  34.         print("Hello World")
  35.         # 创建窗口并设置鼠标回调函数
  36.         cv2.namedWindow("input")
  37.         cv2.setMouseCallback("input", self.onmouse)
  38.         # 读取图像并创建副本及掩码
  39.         self.img=cv2.imread("./grabc/lena.png")
  40.         self.img2=self.img.copy()
  41.         self.mask= np.zeros(self.img.shape[:2], dtype=np.uint8)
  42.         self.output=np.zeros(self.img.shape, dtype=np.uint8)
  43.         
  44.         # 主循环,用于不断显示图像和处理键盘输入
  45.         while True:
  46.             cv2.imshow("input",self.img2)
  47.             cv2.imshow("output",self.output)
  48.             key = cv2.waitKey(1)
  49.             if key == 27:
  50.                 break
  51.             if key == ord("g"):
  52.                 # 初始化模型参数
  53.                 bgdModel= np.zeros((1, 65), np.float64)
  54.                 fgdModel= np.zeros((1, 65), np.float64)
  55.                 # 使用GrabCut算法提取前景
  56.                 cv2.grabCut(self.img, self.mask, self.rect, bgdModel, fgdModel, 1, cv2.GC_INIT_WITH_RECT)
  57.                 mask2= np.where((self.mask==1)|(self.mask==3), 255, 0).astype("uint8")
  58.                 self.output=cv2.bitwise_and(self.img, self.img, mask=mask2)
  59. # 如果作为主程序运行,则创建类实例并运行
  60. if __name__ == "__main__":
  61.     app().run()
复制代码


  • 导入库:
    1. import cv2
    2. import numpy as np
    复制代码
    导入 OpenCV(cv2)和 NumPy(np)库。OpenCV 用于图像处置惩罚,NumPy 用于数值盘算(特别是数组操纵)。
  • 定义 app 类:
    1. class app:
    2.     # ... (类的成员和方法)
    复制代码
    创建一个名为 app 的类,用于封装图像处置惩罚和用户交互的逻辑。

    • 类变量初始化:
      1. flag_rect=False
      2. rect=(0,0,0,0)
      3. startx= 0
      4. starty= 0
      复制代码

      • flag_rect: 布尔值,用于标记是否正在绘制矩形。
      • rect: 一个元组 (x, y, width, height),用于存储矩形的左上角坐标、宽度和高度。
      • startx, starty: 记录矩形绘制的起始点坐标。

    • onmouse 方法(鼠标回调函数):
      1. def onmouse(self, event, x, y, flags, param):
      2.     # ... (鼠标事件处理逻辑)
      复制代码
      这个方法是 OpenCV 鼠标回调函数的实现。当鼠标在窗口中发生事件(按下、抬起、移动等)时,OpenCV 会自动调用这个函数。

      • event: 鼠标事件范例(比方 cv2.EVENT_LBUTTONDOWN、cv2.EVENT_LBUTTONUP、cv2.EVENT_MOUSEMOVE)。
      • x, y: 鼠标指针的当前坐标。
      • flags: 鼠标事件的附加标记(比方 cv2.EVENT_FLAG_LBUTTON 表现左键是否按下)。
      • param: 用户自定义的参数(这里没有效到)。
      鼠标事件处置惩罚逻辑:
           

      • 鼠标左键按下 (cv2.EVENT_LBUTTONDOWN):
        1. if event == cv2.EVENT_LBUTTONDOWN:
        2.     self.flag_rect= True
        3.     self.startx= x
        4.     self.starty= y
        5.     print("down")
        复制代码

        • 将 flag_rect 设置为 True,表现开始绘制矩形。
        • 记录起始点的 x、y 坐标。

      • 鼠标左键抬起 (cv2.EVENT_LBUTTONUP):
        1. elif event == cv2.EVENT_LBUTTONUP:
        2.     self.img2=self.img.copy()
        3.     self.flag_rect= False
        4.     cv2.rectangle(self.img2, (self.startx, self.starty), (x, y), (0, 0, 255), 2)
        5.     self.rect=(min(self.startx,x), min(self.starty,y), abs(self.startx-x), abs(self.starty-y))
        6.     print("up")
        复制代码

        • 将原始图像 self.img 复制到 self.img2,以便在 self.img2 上绘制矩形而不影响原始图像。
        • 将 flag_rect 设置为 False,表现矩形绘制竣事。
        • 使用 cv2.rectangle 在 self.img2 上绘制一个赤色的矩形((0, 0, 255) 表现 BGR 颜色)。
        • 盘算并存储矩形的 (x, y, width, height) 信息到 self.rect。

      • 鼠标移动且左键按下 (cv2.EVENT_MOUSEMOVE 且 cv2.EVENT_FLAG_LBUTTON):
        1. elif event == cv2.EVENT_MOUSEMOVE and (self.flag_rect & cv2.EVENT_FLAG_LBUTTON):
        2.     self.img2=self.img.copy()
        3.     cv2.rectangle(self.img2, (self.startx, self.starty), (x, y), (0, 255, 0), 2)
        4.     print("move")
        复制代码

        • 同样复制原始图像到 self.img2。
        • 在 self.img2 上绘制一个绿色的矩形,实现动态绘制效果。


    • run 方法(主步伐运行函数):
      1. def run(self):
      2.     # ... (主程序逻辑)
      复制代码
      这是步伐的主要实行部分。

      • 打印欢迎信息:
        1. print("Hello World")
        复制代码
      • 创建窗口并设置鼠标回调:
        1. cv2.namedWindow("input")
        2. cv2.setMouseCallback("input", self.onmouse)
        复制代码

        • cv2.namedWindow("input"): 创建一个名为 “input” 的窗口,用于显示图像。
        • cv2.setMouseCallback("input", self.onmouse): 将 self.onmouse 函数设置为 “input” 窗口的鼠标回调函数。

      • 读取图像、创建副本和掩码:
        1. self.img=cv2.imread("./grabc/lena.png")
        2. self.img2=self.img.copy()
        3. self.mask= np.zeros(self.img.shape[:2], dtype=np.uint8)
        4. self.output=np.zeros(self.img.shape, dtype=np.uint8)
        复制代码

        • self.img = cv2.imread("./grabc/lena.png"): 读取图像文件(你需要将 "./grabc/lena.png" 更换为你的图像路径)。
        • self.img2 = self.img.copy(): 创建图像的副本,用于显示和绘制。
        • self.mask = np.zeros(self.img.shape[:2], dtype=np.uint8): 创建一个与图像大小相同的全零掩码数组(self.mask)。这个掩码将用于 GrabCut 算法。
        • self.img.shape[:2] 保证了 mask 与 img 的尺寸一致
        • self.output = np.zeros(self.img.shape, dtype=np.uint8): 创建一个与图像大小相同的全零数组,用于存储 GrabCut 的输出结果。

      • 主循环:
        1. while True:
        2.     cv2.imshow("input",self.img2)
        3.     cv2.imshow("output",self.output)
        4.     key = cv2.waitKey(1)
        5.     if key == 27:
        6.         break
        7.     if key == ord("g"):
        8.         # ... (GrabCut 算法调用)
        复制代码

        • cv2.imshow("input", self.img2): 在 “input” 窗口中显示 self.img2(包罗绘制的矩形)。
        • cv2.imshow("output", self.output): 在 “output” 窗口中显示 self.output(GrabCut 的结果)。
        • key = cv2.waitKey(1): 期待键盘输入,期待时间为 1 毫秒。返回值是按键的 ASCII 码。
        • if key == 27: 如果按下 Esc 键(ASCII 码为 27),退出循环。
        • if key == ord("g"): 如果按下 ‘g’ 键,实行 GrabCut 算法。
          GrabCut 算法调用:
          1. bgdModel= np.zeros((1, 65), np.float64)
          2. fgdModel= np.zeros((1, 65), np.float64)
          3. cv2.grabCut(self.img, self.mask, self.rect, bgdModel, fgdModel, 1, cv2.GC_INIT_WITH_RECT)
          4. mask2= np.where((self.mask==1)|(self.mask==3), 255, 0).astype("uint8")
          5. self.output=cv2.bitwise_and(self.img, self.img, mask=mask2)
          复制代码

          • bgdModel = np.zeros((1, 65), np.float64): 创建一个用于存储配景模子参数的数组。
          • fgdModel = np.zeros((1, 65), np.float64): 创建一个用于存储前景模子参数的数组。
          • cv2.grabCut(...): 调用 GrabCut 算法。

            • self.img: 输入图像。
            • self.mask: 输入/输出掩码。初始时,矩形框外的区域会被自动标记为 cv2.GC_BGD(确定的配景),框内区域为 cv2.GC_PR_FGD(可能的前景)。GrabCut 算法会更新这个掩码。
            • self.rect: 包罗前景的矩形框。
            • bgdModel: 配景模子。
            • fgdModel: 前景模子。
            • 1: 迭代次数。
            • cv2.GC_INIT_WITH_RECT: 初始化模式,表现使用矩形框初始化。

          • mask2 = np.where((self.mask == 1) | (self.mask == 3), 255, 0).astype("uint8"): 根据 GrabCut 更新后的掩码 self.mask 创建一个二值掩码 mask2。

            • self.mask == 1 或 self.mask == 3 表现像素被标记为确定的配景或可能的前景。
            • np.where(...): 将满足条件的像素设置为 255(白色),不满足条件的设置为 0(玄色)。
            • .astype("uint8"): 将掩码的数据范例转换为 uint8。

          • self.output = cv2.bitwise_and(self.img, self.img, mask=mask2): 使用 mask2 作为掩码,对原始图像 self.img 举行按位与操纵,提取前景。




  • 运行 app:
    1. if __name__ == "__main__":
    2.     app().run()
    复制代码
    如果这个脚本是主步伐(而不是被导入的模块),则创建一个 app 类的实例并调用其 run 方法,启动步伐。
3. 运行和使用

  • 预备图像: 预备一张你想要分割的图像,比方 "lena.png"。
  • 运行代码: 运行这段 Python 代码。
  • 绘制矩形: 在弹出的 “input” 窗口中,用鼠标左键拖动绘制一个矩形,将你想要提取的前景包罗在矩形内。
  • 实行 GrabCut: 按下键盘上的 ‘g’ 键。
  • 查看结果: “output” 窗口中会显示分割后的前景图像。
  • 退出: 按下 Esc 键退出步伐。
4. 改进和扩展


  • 多次迭代: 你可以通过增加 cv2.grabCut 函数中的迭代次数(比方 5 或 10)来得到更精细的分割结果。
  • 使用掩码初始化: 除了使用矩形框,你还可以手动绘制掩码来更精确地指定前景和配景区域。这需要将 cv2.GC_INIT_WITH_RECT 改为 cv2.GC_INIT_WITH_MASK,并在 self.mask 中设置相应的像素值:

    • cv2.GC_BGD (0): 确定的配景
    • cv2.GC_FGD (1): 确定的前景
    • cv2.GC_PR_BGD (2): 可能的配景
    • cv2.GC_PR_FGD (3): 可能的前景

  • 交互式修正: 你可以添加更多的鼠标交互,允许用户在 GrabCut 结果的基础上举行手动修正(比方,用画笔标记错误分割的区域)。
  • 参数调整

    • gamma: gamma 值越小, 算法会倾向于将更多的像素标记为前景。
    • lambda: lambda 值控制了平滑项的权重, lambda 值越大, 分割边界越平滑。

希望这个详细的教程对你有帮助!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表