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_LBUTTONDOWN、cv2.EVENT_LBUTTONUP、cv2.EVENT_MOUSEMOVE)。
- x, y: 鼠标指针的当前坐标。
- flags: 鼠标事件的附加标记(比方 cv2.EVENT_FLAG_LBUTTON 表现左键是否按下)。
- param: 用户自定义的参数(这里没有效到)。
鼠标事件处置惩罚逻辑:
- 鼠标左键按下 (cv2.EVENT_LBUTTONDOWN):
- if event == cv2.EVENT_LBUTTONDOWN:
- self.flag_rect= True
- self.startx= x
- self.starty= y
- print("down")
复制代码
- 将 flag_rect 设置为 True,表现开始绘制矩形。
- 记录起始点的 x、y 坐标。
- 鼠标左键抬起 (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.rectangle 在 self.img2 上绘制一个赤色的矩形((0, 0, 255) 表现 BGR 颜色)。
- 盘算并存储矩形的 (x, y, width, height) 信息到 self.rect。
- 鼠标移动且左键按下 (cv2.EVENT_MOUSEMOVE 且 cv2.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):
- # ... (主程序逻辑)
复制代码 这是步伐的主要实行部分。
- 打印欢迎信息:
- 创建窗口并设置鼠标回调:
- cv2.namedWindow("input")
- cv2.setMouseCallback("input", self.onmouse)
复制代码
- cv2.namedWindow("input"): 创建一个名为 “input” 的窗口,用于显示图像。
- cv2.setMouseCallback("input", self.onmouse): 将 self.onmouse 函数设置为 “input” 窗口的鼠标回调函数。
- 读取图像、创建副本和掩码:
- 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 的输出结果。
- 主循环:
- 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 == 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:
- if __name__ == "__main__":
- 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企服之家,中国第一个企服评测及商务社交产业平台。 |