前請提要
前三期(【計算機視覺(二)】常用顏色空間及其轉換)、【計算機視覺(三)】形態學處理、【計算機視覺(四)】輪廓檢測)在介紹基本知識的同時穿插了一個很naive的檢測車牌位置的方法,對參數設置有很強的依賴。后續會慢慢涉及到高級的方法。
本期內容
本期作為一個引子介紹模板匹配法,引出后來的Cascade檢測算法。
一、全等模板
假如我們要在上面這張固定的圖上找出左邊這輛車的位置,一個很簡單的方法就是我們可以先人工把左邊這輛車用“截圖”工具裁剪出來。
然后拿著這張圖在大圖上找,一開始,我們把模板圖放到大圖的左上角,看它們是不是所有像素值都相等,然后往右移一個像素,再看是不是所有值相等,如此遍歷整幅圖,直到找到全等的位置。
這樣的搜尋策略叫做滑動窗口。
實現代碼如下:
# coding: utf-8
import cv2
import numpy as np
'''
函數名:template_match
輸入:
template 模板
img 原圖
輸出:
(x,y) 匹配位置的左上角坐標,找不到返回None
'''
def template_match(template, img):
tpl_h, tpl_w = template.shape[:2]
img_h, img_w = img.shape[:2]
for i in xrange(img_h - tpl_h):
for j in xrange(img_w - tpl_w):
roi = img[i:i+tpl_h, j:j+tpl_w]
if (template == roi).all():
return (j,i)
return None
# 程序入口
def main():
# 整圖
cars = cv2.imread('car_test.jpg')
# 車模板
car_tpl = cars[117:199, 24:246].copy()
pos = template_match(car_tpl, cars)
tpl_h, tpl_w = car_tpl.shape[:2]
if pos is not None:
x,y = pos
cv2.rectangle(cars, (x,y), (x+tpl_w,y+tpl_h), (0,255,0), 2)
cv2.imshow('template', car_tpl)
cv2.imshow('result', cars)
cv2.imwrite('template_match_result.jpg', cars)
cv2.waitKey(0)
if __name__ == '__main__':
main()
這樣做的缺點很明顯,只要有一個像素點跟模板不相等就找不到了,如果只是要找些非常固定的東西那用這種方法是可以的,比如以前做游戲腳本的時候要實現鼠標點到某個固定的技能或道具上。
二、相關系數
這時候要用更靠譜的測度——例如相關系數(除此之外還有平方差等)。
圖像的相關系數的計算方法參考這個公式,是從Matlab的文檔截下來的。
分子是圖像與模板的協方差,分母是它們的標準差的乘積。
具體原理參考知乎的這個回答。
它會返回一個數值表示圖像的相關程度,越相關,值越靠近1,實現代碼如下:
# 輸入灰度圖
def norm_corr(template, img):
tpl_h, tpl_w = template.shape[:2]
img_h, img_w = img.shape[:2]
expand_img = np.zeros((tpl_h+img_h, tpl_w+img_w), dtype=np.uint8)
expand_img[:img_h, :img_w] = img
img_h, img_w = expand_img.shape[:2]
# 圖像均值
tpl_mean = np.mean(template)
# 減均值
tpl_sub_mean = template - tpl_mean
# 標準差
sigma_tpl = np.sum(tpl_sub_mean**2)
# 相關系數圖
corr = np.zeros(img.shape, dtype=np.float32)
for i in xrange(img_h - tpl_h):
for j in xrange(img_w - tpl_w):
roi = expand_img[i:i+tpl_h, j:j+tpl_w]
# 圖像均值
roi_mean = np.mean(roi)
# 減均值
roi_sub_mean = roi - roi_mean
# 標準差
sigma_roi = np.sum(roi_sub_mean**2)
# 協方差
cov = np.sum(tpl_sub_mean * roi_sub_mean)
# 相關系數
corr[i,j] = cov / np.sqrt(sigma_tpl * sigma_roi)
# 歸一化到0-255
corr_max = corr.max()
corr_min = corr.min()
print 'max = ', corr_max
print 'min = ', corr_min
if corr_max != corr_min:
corr = 255 * (corr - corr_min) / (corr_max - corr_min)
return corr.astype(np.uint8)
要注意輸入是灰度圖,最后輸出是一張表示每一個位置上的相關系數的圖,歸一化到0-255就可以顯示出來了。再次使用上面的模板圖和大圖,得到的相關系數圖如下:
圖中越亮的地方表示把模板的左上角在大圖的對應位置的可能性越大。
我們可以把相關圖上值超過127(最大255,可以自行設置這個閾值)的地方用矩形框標出來。
代碼如下:
idx = np.where(corr > 0.5 * 255)
rows = idx[0]
cols = idx[1]
rects = []
for r,c in zip(rows, cols):
if c+tpl_w < img_w and r+tpl_h < img_h:
rects.append((c,r,c+tpl_w,r+tpl_h))
for rect in rects:
tx,ty,bx,by = rect
cv2.rectangle(cars, (tx,ty), (bx,by), (0,255,0), 2)
效果如下:
可以看到雖然用的模板是左邊的車,但由于兩臺車很相似,所以右邊的車也被檢出來了,但是框非常的多,我們可以想辦法做去重,把頂點靠近的矩形框當作一個子集,最后分別給出各個矩形集的平均位置,代碼如下,可以根據需要子集改進:
def group_rects(rects, diff=20):
groups = [[rects[0]]]
for rect in rects[1:]:
tx,ty,bx,by = rect
found = False
for gr in groups:
for gre in gr:
if abs(gre[0]-tx) < diff and abs(gre[1]-ty) < diff and abs(gre[2]-bx) < diff and abs(gre[3]-by) < diff:
gr.append(rect)
found = True
break
if found:
break
if not found:
groups.append([rect])
result = []
for group in groups:
result.append(np.array(group).mean(axis=0).astype(np.int32).tolist())
return result
這樣就得到了很好的兩個框。
總結
即使改變了使用的距離函數,我們也只使用了一個數據(圖像)就希望能檢測到其他的同類物體,的確是很以偏概全的想法。以檢測車子的例子來說,我們是把一臺特定的車當作模板,而沒有從更接近本質的角度去解構這個問題,假設我們能人為的定義車子該長什么樣也許就能很好的解決這個問題,比如說車有輪子、車窗、車燈、整體是流線型的,等等。這些性質可以稱為車子的特征(feature)。計算機視覺有很大部分研究的問題都在圍繞著如何更好的描述物體,也就是如何得到物體更好的特征。特征可能是級聯的,意思是說有輪子是車子的特征,輪子也有自己的特征(圓的,黑的),特征總是從低層的只包含結構、形狀等信息往高層的更不可描述的信息走。發展到現在,特征可以分為人為設計和通過機器學習的方法學習出來兩種,人為設計的特征如HOG、Haar等,有著固定的計算過程,可以手工計算出來,在深度學習大熱的當今已經慢慢被新晉行業的人視為“傳統的技術”,但在我看來這兩者并沒有那么大的不同,沒有必要放棄這些“old-school”的技術,這樣只會造成“拿起錘子,看什么都是釘子”的想法。