1. 问题
前段时间正在写一个课程作业,当时遇到一个问题,如何将下面这张图里的鱼抠出来:
如果要求不能使用PS软件抠图,有其它办法吗?
于是脑子里出现了一些奇奇怪怪的想法:
2. 方案
方案一:用 canny 边缘检测一下,在通过得到的边缘图像做膨胀腐蚀,最后选择区域,再膨胀腐蚀得到鱼的 shape
emmm 理论上可以,但效果可能不太行
方案二:查看图片的 RGB 颜色空间分布,选择合适的颜色区域作为 mask:
要不先看看 RGB 空间中的颜色分布吧:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
if __name__ == '__main__':
# LOAD NEMO IMAGE
nemo = cv.imread('./resources/313.bmp')
rgb_nemo = cv.cvtColor(nemo, cv.COLOR_BGR2RGB)
# 显示 RGB 空间
fig = plt.figure(1)
plt.subplot(1, 2, 1)
plt.imshow(rgb_nemo)
plt.axis('off')
r, g, b = cv.split(rgb_nemo)
rows, cols, d = rgb_nemo.shape
pixel_colors = (rgb_nemo.reshape(rows*cols, 3)/255).tolist()
axis = fig.add_subplot(1, 2, 2, projection="3d")
axis.scatter(r.flatten(), g.flatten(), b.flatten(),
facecolors=pixel_colors, marker='.')
axis.set_xlabel('R')
axis.set_ylabel('G')
axis.set_zlabel('B')
plt.show()
接下来需要选择鱼身体的颜色大致分布范围,鱼的颜色大致为橙色和白色,但 RGB 空间颜色分布不好分割!
方案二:使用 HSV 颜色空间分割图像:
先了解一下 HSV:
— ipad 截图糊了点
动手试试:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
if __name__ == '__main__':
# LOAD NEMO IMAGE
nemo = cv.imread('./resources/313.bmp')
rgb_nemo = cv.cvtColor(nemo, cv.COLOR_BGR2RGB)
# 显示 HSV 空间
fig = plt.figure(2)
plt.subplot(1, 2, 1)
plt.imshow(rgb_nemo)
plt.axis('off')
hsv_nemo = cv.cvtColor(rgb_nemo, cv.COLOR_RGB2HSV)
h, s, v = cv.split(hsv_nemo)
rows, cols, d = rgb_nemo.shape
pixel_colors = (rgb_nemo.reshape(rows*cols, 3)/255).tolist()
axis = fig.add_subplot(1, 2, 2, projection="3d")
axis.scatter(h.flatten(), s.flatten(), v.flatten(),
facecolors=pixel_colors, marker='.')
axis.set_xlabel('Hue')
axis.set_ylabel('Saturation')
axis.set_zlabel('Value')
plt.show()
接着选择出红色分量和白色分量的区间,这个我会,HSV 空间中不同颜色和不同饱和度的颜色间隔较大,容易选择区间!
比如选择这样的颜色范围:
light_orange = (5, 160, 120)
dark_orange = (20, 255, 255)
light_white = (35, 0, 165)
dark_light = (255, 120, 255)
其中,light_orange 到 dark_orange 是橙色的 HSV 的范围,H ∈[5, 20], S ∈[166, 255]…
接下来我们使用橙色和白色分别对图像分割(分割前可能需要选择 ROI ),最后将两个分割结果融合:
# 利用 cv.inRange 生成二值化的模板
mask1 = cv.inRange(hsv_nemo, light_orange, dark_orange)
mask2 = cv.inRange(hsv_nemo, light_white, dark_light)
mask12 = np.array((mask1 + mask2) > 0, dtype=mask1.dtype) # 模板合并
nemo_mask1 = cv.bitwise_and(rgb_nemo, rgb_nemo, mask=mask1)
nemo_mask2 = cv.bitwise_and(rgb_nemo, rgb_nemo, mask=mask2)
nemo_mask12 = cv.bitwise_and(rgb_nemo, rgb_nemo, mask=mask12)
fig = plt.figure(3)
plt.subplot(2, 3, 1), plt.imshow(mask1, cmap='gray'), plt.axis('off'), plt.title('mask1')
plt.subplot(2, 3, 4), plt.imshow(nemo_mask1), plt.axis('off'), plt.title('nemo_mask1')
plt.subplot(2, 3, 2), plt.imshow(mask2, cmap='gray'), plt.axis('off'), plt.title('mask2')
plt.subplot(2, 3, 5), plt.imshow(nemo_mask2), plt.axis('off'), plt.title('nemo_mask2')
plt.subplot(2, 3, 3), plt.imshow(mask12, cmap='gray'), plt.axis('off'), plt.title('mask12')
plt.subplot(2, 3, 6), plt.imshow(nemo_mask12), plt.axis('off'), plt.title('nemo_mask12')
plt.show()
效果很棒 !
接着我们处理一下 mask 中的不连续点和毛刺,之间膨胀腐蚀一下就 ok 了:
# 膨胀腐蚀
plt.figure()
plt.subplot(1, 3, 1), plt.imshow(rgb_nemo)
plt.title('RGB_Nemo'), plt.axis('off')
kernel = np.ones((9, 9), np.uint8)
mask = cv.morphologyEx(mask12, cv.MORPH_CLOSE, kernel)
ROI = np.zeros(mask.shape, np.uint8)
ROI[0:150, 50:200] = 1
mask = mask * ROI
plt.subplot(1, 3, 2), plt.imshow(mask, cmap='gray')
plt.title('Mask after opening'), plt.axis('off')
nemo_ROI = rgb_nemo * cv.merge([mask, mask, mask])
plt.subplot(1, 3, 3), plt.imshow(nemo_ROI)
plt.title('Nemo'), plt.axis('off')
plt.show()
Perfect!
3. 程序代码
为了方便使用,我把程序打包了,在后面需要分割 nemo 鱼的程序中调用即可
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def generate_nemo_mask(img):
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
hsv_img = cv.cvtColor(rgb_img, cv.COLOR_RGB2HSV)
light_orange = (5, 100, 120)
dark_orange = (20, 255, 255)
light_white = (35, 0, 160)
dark_light = (255, 160, 255)
mask1 = cv.inRange(hsv_img, light_orange, dark_orange)
mask2 = cv.inRange(hsv_img, light_white, dark_light)
mask = np.array((mask1 + mask2) > 0, dtype=mask1.dtype)
kernel = np.ones((9, 9), np.uint8)
mask = cv.morphologyEx(mask, cv.MORPH_CLOSE, kernel)
ROI = np.zeros(mask.shape, np.uint8)
ROI[0:166, 50:200] = 1
mask = mask * ROI
return mask
注意:如果是分割其他图像,则需要重新设置 HSV 参数和 ROI 区域,可以增加 function 的参数~
REFERENCES:
- http://www.360doc.com/content/18/1003/15/13328254_791618642.shtml