作业目标

对下面的图进行图像分割(分割出人和球)
image.png

算法简介

  • 算法主要基于以下知识:

k均值聚类
高斯混合模型建模(GMM)
max flow/min cut

  • GrabCut算法的实现步骤:
  1. 在图片中定义(一个或者多个)包含物体的矩形。 矩形外的区域被自动认为是背景。
  2. 对于用户定义的矩形区域,可用背景中的数据来区分它里面的前景和背景区域。
  3. 用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或者背景。
  4. 图像中的每一个像素都被看做通过虚拟边与周围像素相连接,而每条边都有一个属于前景或者背景的概率,这是基于它与周边像素颜色上的相似性。
  5. 每一个像素(即算法中的节点)会与一个前景或背景节点连接。
  6. 在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。

    实现代码

  1. import numpy as np
  2. import cv2
  3. # 鼠标事件的回调函数
  4. def on_mouse(event, x, y, flag, param):
  5. global rect
  6. global leftButtonDowm
  7. global leftButtonUp
  8. # 鼠标左键按下
  9. if event == cv2.EVENT_LBUTTONDOWN:
  10. rect[0] = x
  11. rect[2] = x
  12. rect[1] = y
  13. rect[3] = y
  14. leftButtonDowm = True
  15. leftButtonUp = False
  16. # 移动鼠标事件
  17. if event == cv2.EVENT_MOUSEMOVE:
  18. if leftButtonDowm and not leftButtonUp:
  19. rect[2] = x
  20. rect[3] = y
  21. # 鼠标左键松开
  22. if event == cv2.EVENT_LBUTTONUP:
  23. if leftButtonDowm and not leftButtonUp:
  24. x_min = min(rect[0], rect[2])
  25. y_min = min(rect[1], rect[3])
  26. x_max = max(rect[0], rect[2])
  27. y_max = max(rect[1], rect[3])
  28. rect[0] = x_min
  29. rect[1] = y_min
  30. rect[2] = x_max
  31. rect[3] = y_max
  32. leftButtonDowm = False
  33. leftButtonUp = True
  34. # 读入图片
  35. img = cv2.imread('messi5.jpg')
  36. # 掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果
  37. mask = np.zeros(img.shape[:2], np.uint8)
  38. # 背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
  39. bgdModel = np.zeros((1, 65), np.float64)
  40. # fgdModel——前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
  41. fgdModel = np.zeros((1, 65), np.float64)
  42. # 用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
  43. rect = [0, 0, 0, 0]
  44. # 鼠标左键按下
  45. leftButtonDowm = False
  46. # 鼠标左键松开
  47. leftButtonUp = True
  48. # 指定窗口名来创建窗口
  49. cv2.namedWindow('img')
  50. # 设置鼠标事件回调函数 来获取鼠标输入
  51. cv2.setMouseCallback('img', on_mouse)
  52. # 显示图片
  53. cv2.imshow('img', img)
  54. while cv2.waitKey(2) == -1:
  55. # 左键按下,画矩阵
  56. if leftButtonDowm and not leftButtonUp:
  57. img_copy = img.copy()
  58. # 在img图像上,绘制矩形 线条颜色为green 线宽为2
  59. cv2.rectangle(img_copy, (rect[0], rect[1]), (rect[2], rect[3]), (0, 255, 0), 2)
  60. # 显示图片
  61. cv2.imshow('img', img_copy)
  62. # 左键松开,矩形画好
  63. elif not leftButtonDowm and leftButtonUp and rect[2] - rect[0] != 0 and rect[3] - rect[1] != 0:
  64. # 转换为宽度高度
  65. rect[2] = rect[2] - rect[0]
  66. rect[3] = rect[3] - rect[1]
  67. rect_copy = tuple(rect.copy())
  68. rect = [0, 0, 0, 0]
  69. # 物体分割
  70. cv2.grabCut(img, mask, rect_copy, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
  71. mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
  72. img_show = img * mask2[:, :, np.newaxis]
  73. # 显示图片分割后结果
  74. cv2.imshow('grabcut', img_show)
  75. # 显示原图
  76. cv2.imshow('img', img)
  77. cv2.waitKey(0)
  78. cv2.destroyAllWindows()

实验结果

原始图片 image.png
勾选目标区域 image.png
分割结果 image.png

分割出来的效果不是很好,脚下的草可以通过写代码去掉特定的颜色,但是考虑到这样做没有普遍性,换一张图就完全行不通了,于是没有写。