当前位置: 首页 > article >正文

OpenCV GrabCut 图像分割

1. GrabCut 算法简介

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

核心思想:

  • 构建图模型: 将图像像素视为图的节点,相邻像素之间通过边连接。边的权重反映了像素之间的相似度(颜色、梯度等)。
  • 用户交互: 用户通过矩形框指定一个包含前景的区域。矩形框外的区域被认为是确定的背景(GC_BGD),框内的区域被认为是可能的前景(GC_PR_FGD)。
  • 高斯混合模型(GMM): 分别为前景和背景构建颜色分布的高斯混合模型。每个像素根据其颜色属于前景或背景的概率被标记。
  • 迭代优化: 通过最小化能量函数(考虑像素颜色与 GMM 的匹配度以及相邻像素标签的平滑性),不断更新像素的标签(前景/背景)和 GMM 参数,直到收敛。

2. 代码逐步解析

我们来详细分析你提供的 Python 代码,理解每一步的作用。

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("down")
        # 当鼠标左键抬起时,绘制矩形并记录矩形信息
        elif event == cv2.EVENT_LBUTTONUP:
            self.img2=self.img.copy()
            self.flag_rect= False
            cv2.rectangle(self.img2, (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("up")
        # 当鼠标移动且左键按下时,动态绘制矩形
        elif event == cv2.EVENT_MOUSEMOVE and (self.flag_rect & cv2.EVENT_FLAG_LBUTTON):
            self.img2=self.img.copy()
            cv2.rectangle(self.img2, (self.startx, self.starty), (x, y), (0, 255, 0), 2)
            print("move")
    
    # 主程序运行函数
    def run(self):
        print("Hello World")

        # 创建窗口并设置鼠标回调函数
        cv2.namedWindow("input")
        cv2.setMouseCallback("input", self.onmouse)

        # 读取图像并创建副本及掩码
        self.img=cv2.imread("./grabc/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, dtype=np.uint8)
        
        # 主循环,用于不断显示图像和处理键盘输入
        while True:
            cv2.imshow("input",self.img2)
            cv2.imshow("output",self.output)
            key = cv2.waitKey(1)
            if key == 27:
                break
            if key == ord("g"):
                # 初始化模型参数
                bgdModel= np.zeros((1, 65), np.float64)
                fgdModel= np.zeros((1, 65), np.float64)
                # 使用GrabCut算法提取前景
                cv2.grabCut(self.img, 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.img, self.img, mask=mask2)

# 如果作为主程序运行,则创建类实例并运行
if __name__ == "__main__":
    app().run()
  • 导入库:

    import cv2
    import numpy as np
    

    导入 OpenCV(cv2)和 NumPy(np)库。OpenCV 用于图像处理,NumPy 用于数值计算(特别是数组操作)。

  • 定义 app 类:

    class app:
        # ... (类的成员和方法)
    

    创建一个名为 app 的类,用于封装图像处理和用户交互的逻辑。

    • 类变量初始化:

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

      def onmouse(self, event, x, y, flags, param):
          # ... (鼠标事件处理逻辑)
      

      这个方法是 OpenCV 鼠标回调函数的实现。当鼠标在窗口中发生事件(按下、抬起、移动等)时,OpenCV 会自动调用这个函数。

      • event: 鼠标事件类型(例如 cv2.EVENT_LBUTTONDOWNcv2.EVENT_LBUTTONUPcv2.EVENT_MOUSEMOVE)。
      • x, y: 鼠标指针的当前坐标。
      • flags: 鼠标事件的附加标志(例如 cv2.EVENT_FLAG_LBUTTON 表示左键是否按下)。
      • param: 用户自定义的参数(这里没有用到)。

      鼠标事件处理逻辑:

      1. 鼠标左键按下 (cv2.EVENT_LBUTTONDOWN):

        if event == cv2.EVENT_LBUTTONDOWN:
            self.flag_rect= True
            self.startx= x
            self.starty= y
            print("down")
        
        • flag_rect 设置为 True,表示开始绘制矩形。
        • 记录起始点的 xy 坐标。
      2. 鼠标左键抬起 (cv2.EVENT_LBUTTONUP):

        elif event == cv2.EVENT_LBUTTONUP:
            self.img2=self.img.copy()
            self.flag_rect= False
            cv2.rectangle(self.img2, (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("up")
        
        • 将原始图像 self.img 复制到 self.img2,以便在 self.img2 上绘制矩形而不影响原始图像。
        • flag_rect 设置为 False,表示矩形绘制结束。
        • 使用 cv2.rectangleself.img2 上绘制一个红色的矩形((0, 0, 255) 表示 BGR 颜色)。
        • 计算并存储矩形的 (x, y, width, height) 信息到 self.rect
      3. 鼠标移动且左键按下 (cv2.EVENT_MOUSEMOVEcv2.EVENT_FLAG_LBUTTON):

        elif event == cv2.EVENT_MOUSEMOVE and (self.flag_rect & cv2.EVENT_FLAG_LBUTTON):
            self.img2=self.img.copy()
            cv2.rectangle(self.img2, (self.startx, self.starty), (x, y), (0, 255, 0), 2)
            print("move")
        
        • 同样复制原始图像到 self.img2
        • self.img2 上绘制一个绿色的矩形,实现动态绘制效果。
    • run 方法(主程序运行函数):

      def run(self):
          # ... (主程序逻辑)
      

      这是程序的主要执行部分。

      1. 打印欢迎信息:

        print("Hello World")
        
      2. 创建窗口并设置鼠标回调:

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

        self.img=cv2.imread("./grabc/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, 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 的输出结果。
      4. 主循环:

        while True:
            cv2.imshow("input",self.img2)
            cv2.imshow("output",self.output)
            key = cv2.waitKey(1)
            if key == 27:
                break
            if key == ord("g"):
                # ... (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 算法调用:

          bgdModel= np.zeros((1, 65), np.float64)
          fgdModel= np.zeros((1, 65), np.float64)
          cv2.grabCut(self.img, 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.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 == 1self.mask == 3 表示像素被标记为确定的背景或可能的前景。
            • np.where(...): 将满足条件的像素设置为 255(白色),不满足条件的设置为 0(黑色)。
            • .astype("uint8"): 将掩码的数据类型转换为 uint8
          • self.output = cv2.bitwise_and(self.img, self.img, mask=mask2): 使用 mask2 作为掩码,对原始图像 self.img 进行按位与操作,提取前景。
  • 运行 app

    if __name__ == "__main__":
        app().run()
    

    如果这个脚本是主程序(而不是被导入的模块),则创建一个 app 类的实例并调用其 run 方法,启动程序。

3. 运行和使用

  1. 准备图像: 准备一张你想要分割的图像,例如 "lena.png"
  2. 运行代码: 运行这段 Python 代码。
  3. 绘制矩形: 在弹出的 “input” 窗口中,用鼠标左键拖动绘制一个矩形,将你想要提取的前景包含在矩形内。
  4. 执行 GrabCut: 按下键盘上的 ‘g’ 键。
  5. 查看结果: “output” 窗口中会显示分割后的前景图像。
  6. 退出: 按下 Esc 键退出程序。

4. 改进和扩展

  • 多次迭代: 你可以通过增加 cv2.grabCut 函数中的迭代次数(例如 510)来获得更精细的分割结果。
  • 使用掩码初始化: 除了使用矩形框,你还可以手动绘制掩码来更精确地指定前景和背景区域。这需要将 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 值越大, 分割边界越平滑。

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


http://www.kler.cn/a/576635.html

相关文章:

  • iSpiik产品说:抖音短视频,为什么开了自动连播?
  • 日语学习-日语知识点小记-构建基础-JLPT-N4N5阶段(17):会话练习
  • 【Linux实践系列】:用c语言实现一个shell外壳程序
  • android中activity1和activity2中接收定时消息
  • 【selenium工具操作web页面中的下拉框元素 】
  • 机器学习的下一个前沿是因果推理吗?——探索机器学习的未来方向!
  • 工具介绍《HACKBAR V2》
  • leetcode面试题-------链表相交
  • c语言笔记 fgets
  • Maven匹配机制和仓库库设置
  • *算法中的数据结构(3)
  • 【神经网络】python实现神经网络(一)——数据集获取
  • 【hello git】git rebase、git merge、git stash、git cherry-pick
  • 实现Django和Transformers 构建智能客服大模型(模拟订单系统)
  • 【每日学点HarmonyOS Next知识】双向传值问题、子组件半径、VIdeo标签下载隐藏、字符串替换、路由问题
  • 2025年科技趋势深度解析:从“人工智能+”到量子跃迁的技术革命
  • Qt:多线程
  • 通过Nacos API实现微服务不间断部署
  • Linux中的序列化和反序列化与网络计算器
  • 2025系统架构师(一考就过):案例之五:典型架构、架构演化、人工智能、云计算、大数据