最近在自学游戏开发里面的图形算法,需要提取某些图片的前景内容,替换掉原来的背景。如果是几张图用PS处理一下就行了,但图片量比较打,还是写一个程序比较好。
为了解决这个问题,我接触了opencv这个库,突然觉得这玩意太牛逼了,不光可以处理图片,还内置很多人工智能算法,于是暂时放弃了游戏开发,转战计算机视觉。
学了几天基础知识,刚开始觉得有好多种方法都可以提取图片的前景内容,但用得都不理想。原因有以下2个:
1、教材的案例都太简单了,只是为了更简单的讲解某些概念和某些函数的用法。
2、要想处理实际问题,必须综合灵活运用书本里的知识,书本里的方法都是有各种条件限制的。
话不多说了,下面直接进入正题。
提取前景我找到了以下几种方法:
1)用掩膜进行位运算,这只适合背景颜色单一的使用场景
2)使用grabcut,但需要对图像输入用户交互参数
3)边缘提取,边缘好提取,但图片颜色复杂的话,轮廓不好提取。
4)轮廓检测+洪水填充法,图片轮廓清晰且背景颜色简单好提取,当然复杂的也可以提取出来,但轮廓必须有完整的闭合区域。
5)使用HSV颜色空间颜色区域提取的方法,提取出前景或者背景。这种方法只局限于背景颜色与前景颜色完全隔离的情况下使用,当前景中包含了背景的颜色,这种方法就不好用了。
注意:有的看着颜色单一的背景颜色,其实并不单一,为了增加亮度和良好的视觉效果,即使单纯灰色背景都有几千种,必须在RGB颜色空间下,颜色有1600多万种。
下面介绍一下我自己的方法:
第1步:用canny函数得到最佳边缘
import cv2
IMAGE_NAME = "bird.png"
SAVE_IMAGE_NAME = "canny_"+IMAGE_NAME
img = cv2.imread(IMAGE_NAME)
img2gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
c_canny_img = cv2.Canny(img2gray,50,150)
cv2.imshow('mask',c_canny_img)
k = cv2.waitKey(500) & 0xFF
if k == 27:
cv2.destroyAllWindows()
第2步:对边缘像素周围的像素用进行迭代像素填充
实现思路:
定义一个3X3或者5X5的卷积核,遍历这张边缘图片的每一个像素,当遇到像素点为白色(255,255,255)时,对它周围的像素全部置成白色。有可能第一步效果并不理想,不过没关系,多迭代几次就好了。
代码如下:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
img = cv2.imread('second_bird.png')
rows,cols,ch = img.shape
SIZE = 3 #卷积核大小
P = int(SIZE/2)
BLACK = [0,0,0]
WHITE = [255,255,255]
BEGIN = False
BP = []
for row in range(P,rows-P,1):
for col in range(P,cols-P,1):
#print(img[row,col])
if (img[row, col] == WHITE).all():
kernal = []
for i in range(row-P,row+P+1,1):
for j in range(col-P,col+P+1,1):
kernal.append(img[i,j])
if (img[i,j] == BLACK).all():
#print(i,j)
BP.append([i,j])
print(len(BP))
uniqueBP = np.array(list(set([tuple(c) for c in BP])))
print(len(uniqueBP))
for x,y in uniqueBP:
img[x,y] = WHITE
cv2.imshow('img',img)
cv2.imwrite('second_bird.png',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
效果如下:
其实到这一步已经差不多了,前景图片中间有些小黑点,两种方法解决:
1)用上述的方法再迭代一次,缺陷是如果用5*5的核的话,边缘可能可能会扩大4个像素。
2)运用形态学的方法进行腐蚀膨胀一下。
第3步:用掩膜位运算提取白色区域的内容。
代码如下:
# -*- coding:utf-8 -*-
import cv2
img = cv2.imread('bird.png')
img2 = cv2.imread('second_bird.png')
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
print(img2gray.shape)
mask = cv2.bitwise_and(img,img,mask=img2gray)
cv2.imshow('mask',mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
效果如下:
最后再说明2点:
1)上述程序我分别用了3个程序文件来实现,只是为了快速调试,后期我会把完整代码贴出来
2)最终结果有些瑕疵,但那都不是问题,用形态学简单处理一下就OK