为了让我们后续的应用更加专注于目标,我们需要把目标从图像中抠出来,以便于后续的使用。比如我们要识别汽车车身的颜色,首先要把车周围背景过滤,然后再进行车身颜色像素统计。
实现抠图有两种方式,第一种是比较简单的,一键运行就行了,但是效果不是很理想,只是能减少背景的干扰,优点就是能批量处理想要的图片;第二种就精致得多,可以人工慢慢地把前景和背景区分开来。
本文使用的办法是OpenCV自带的grabcut函数,有论文作为支撑,有想了解的可以去搜一下。
论文名称:“GrabCut” — Interactive Foreground Extraction using Iterated Graph Cuts
第一种办法
import cv2 as cv
import numpy as np
filename=r'E:\AI_projects\1-1.jpg'
img = cv.imread(filename)
img2 = img.copy() # a copy of original image
mask = np.zeros(img.shape[:2], dtype = np.uint8) # mask initialized to PR_BG
output = np.zeros(img.shape, np.uint8) # output image to be shown
rect_or_mask = 0
rect=(2,1,img.shape[0],img.shape[1])
bgdmodel = np.zeros((1, 65), np.float64)
fgdmodel = np.zeros((1, 65), np.float64)
cv.grabCut(img2, mask, rect, bgdmodel, fgdmodel, 1, cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask==1) + (mask==3), 255, 0).astype('uint8')
output = cv.bitwise_and(img2, img2, mask=mask2)
cv.imshow('output', output)
cv.imshow('input', img)
cv.waitKey(0)
cv.destroyAllWindows()
从百度上下载一张美女图片进行实验,效果图如下
从实际图像库中拿一张真实的图片进行实验。如果直接用HSV空间进行图像颜色转换并统计白色区域,可能会误判车身的颜色,因为路面的颜色和车身颜色会产生干扰,这时候能抠出车辆轮廓是最好的,实现效果如下:
完完全全抠出物体的轮廓这个是做不到的,这个只是得到大致的,让我们更加关注目标而已,也没必要完完全全把物体轮廓弄出来,如果真的要这么细致,就要考虑第二种办法了。
~
第二种办法
第二种办法是可以人工进行修改前景和背景,提高精确度。
代码第一行提供本文的原出处地址。 https://github.com/opencv/opencv/blob/master/samples/python/grabcut.py
# https://github.com/opencv/opencv/blob/master/samples/python/grabcut.py
#!/usr/bin/env python
'''
===============================================================================
Interactive Image Segmentation using GrabCut algorithm.
This sample shows interactive image segmentation using grabcut algorithm.
USAGE:
python grabcut.py <filename>
README FIRST:
Two windows will show up, one for input and one for output.
At first, in input window, draw a rectangle around the object using the
right mouse button. Then press 'n' to segment the object (once or a few times)
For any finer touch-ups, you can press any of the keys below and draw lines on
the areas you want. Then again press 'n' to update the output.
Key '0' - To select areas of sure background
Key '1' - To select areas of sure foreground
Key '2' - To select areas of probable background
Key '3' - To select areas of probable foreground
Key 'n' - To update the segmentation
Key 'r' - To reset the setup
Key 's' - To save the results
===============================================================================
'''
# Python 2/3 compatibility
from __future__ import print_function
import numpy as np
import cv2 as cv
import sys
class App():
BLUE = [255,0,0] # rectangle color
RED = [0,0,255] # PR BG
GREEN = [0,255,0] # PR FG
BLACK = [0,0,0] # sure BG
WHITE = [255,255,255] # sure FG
DRAW_BG = {'color' : BLACK, 'val' : 0}
DRAW_FG = {'color' : WHITE, 'val' : 1}
DRAW_PR_BG = {'color' : RED, 'val' : 2}
DRAW_PR_FG = {'color' : GREEN, 'val' : 3}
# setting up flags
rect = (0,0,1,1)
drawing = False # flag for drawing curves
rectangle = False # flag for drawing rect
rect_over = False # flag to check if rect drawn
rect_or_mask = 100 # flag for selecting rect or mask mode
value = DRAW_FG # drawing initialized to FG
thickness = 3 # brush thickness
def onmouse(self, event, x, y, flags, param):
# Draw Rectangle
if event == cv.EVENT_RBUTTONDOWN:
self.rectangle = True
self.ix, self.iy = x,y
elif event == cv.EVENT_MOUSEMOVE:
if self.rectangle == True:
self.img = self.img2.copy()
cv.rectangle(self.img, (self.ix, self.iy), (x, y), self.BLUE, 2)
self.rect = (min(self.ix, x), min(self.iy, y), abs(self.ix - x), abs(self.iy - y))
self.rect_or_mask = 0
elif event == cv.EVENT_RBUTTONUP:
self.rectangle = False
self.rect_over = True
cv.rectangle(self.img, (self.ix, self.iy), (x, y), self.BLUE, 2)
self.rect = (min(self.ix, x), min(self.iy, y), abs(self.ix - x), abs(self.iy - y))
self.rect_or_mask = 0
print(" Now press the key 'n' a few times until no further change \n")
# draw touchup curves
if event == cv.EVENT_LBUTTONDOWN:
if self.rect_over == False:
print("first draw rectangle \n")
else:
self.drawing = True
cv.circle(self.img, (x,y), self.thickness, self.value['color'], -1)
cv.circle(self.mask, (x,y), self.thickness, self.value['val'], -1)
elif event == cv.EVENT_MOUSEMOVE:
if self.drawing == True:
cv.circle(self.img, (x, y), self.thickness, self.value['color'], -1)
cv.circle(self.mask, (x, y), self.thickness, self.value['val'], -1)
elif event == cv.EVENT_LBUTTONUP:
if self.drawing == True:
self.drawing = False
cv.circle(self.img, (x, y), self.thickness, self.value['color'], -1)
cv.circle(self.mask, (x, y), self.thickness, self.value['val'], -1)
def run(self):
# Loading images
if len(sys.argv) == 2:
filename = sys.argv[1] # for drawing purposes
else:
print("No input image given, so loading default image, lena.jpg \n")
print("Correct Usage: python grabcut.py <filename> \n")
filename = r'E:\AI_projects\girls.jpg'
self.img = cv.imread(cv.samples.findFile(filename))
self.img2 = self.img.copy() # a copy of original image
self.mask = np.zeros(self.img.shape[:2], dtype = np.uint8) # mask initialized to PR_BG
self.output = np.zeros(self.img.shape, np.uint8) # output image to be shown
# input and output windows
cv.namedWindow('output')
cv.namedWindow('input')
cv.setMouseCallback('input', self.onmouse)
cv.moveWindow('input', self.img.shape[1]+10,90)
print(" Instructions: \n")
print(" Draw a rectangle around the object using right mouse button \n")
while(1):
cv.imshow('output', self.output)
cv.imshow('input', self.img)
k = cv.waitKey(1)
# key bindings
if k == 27: # esc to exit
break
elif k == ord('0'): # BG drawing
print(" mark background regions with left mouse button \n")
self.value = self.DRAW_BG
elif k == ord('1'): # FG drawing
print(" mark foreground regions with left mouse button \n")
self.value = self.DRAW_FG
elif k == ord('2'): # PR_BG drawing
self.value = self.DRAW_PR_BG
elif k == ord('3'): # PR_FG drawing
self.value = self.DRAW_PR_FG
elif k == ord('s'): # save image
bar = np.zeros((self.img.shape[0], 5, 3), np.uint8)
res = np.hstack((self.img2, bar, self.img, bar, self.output))
cv.imwrite('grabcut_output.png', res)
print(" Result saved as image \n")
elif k == ord('r'): # reset everything
print("resetting \n")
self.rect = (0,0,1,1)
self.drawing = False
self.rectangle = False
self.rect_or_mask = 100
self.rect_over = False
self.value = self.DRAW_FG
self.img = self.img2.copy()
self.mask = np.zeros(self.img.shape[:2], dtype = np.uint8) # mask initialized to PR_BG
self.output = np.zeros(self.img.shape, np.uint8) # output image to be shown
elif k == ord('n'): # segment the image
print(""" For finer touchups, mark foreground and background after pressing keys 0-3
and again press 'n' \n""")
try:
bgdmodel = np.zeros((1, 65), np.float64)
fgdmodel = np.zeros((1, 65), np.float64)
if (self.rect_or_mask == 0): # grabcut with rect
cv.grabCut(self.img2, self.mask, self.rect, bgdmodel, fgdmodel, 1, cv.GC_INIT_WITH_RECT)
self.rect_or_mask = 1
elif (self.rect_or_mask == 1): # grabcut with mask
cv.grabCut(self.img2, self.mask, self.rect, bgdmodel, fgdmodel, 1, cv.GC_INIT_WITH_MASK)
except:
import traceback
traceback.print_exc()
mask2 = np.where((self.mask==1) + (self.mask==3), 255, 0).astype('uint8')
self.output = cv.bitwise_and(self.img2, self.img2, mask=mask2)
print('Done')
if __name__ == '__main__':
print(__doc__)
App().run()
cv.destroyAllWindows()
使用办法:
1、用鼠标右键拖拽出目标区域,2、按n按键,3、回车
可以看出图中两个头之间、下半身等多处抠图是不理想的,这时就可以进行人工扣除,去除不理想的地方。
至此,我们应该大概了解这两种抠图的使用方法了。
说明:扣除的背景以黑色进行填充,灰度值设置为0,这个值在颜色识别时是需要知道的,因为我们HSV空间中黑色的范围是从0开始的,所以我们颜色空间需要统计黑色时要从大于0的数值开始。
关于颜色判断,将在另一篇文字中进行讲解,敬请关注。