OpenCV—python 反向投影 ROI
一、反向投影概念
在計算機視覺這一塊,影象反向投影的最終目的是獲取ROI然後實現對ROI區域的標註、識別、測量等影象處理與分析,是計算機視覺與人工智慧的常見方法之一。 如果一幅影象的區域中顯示的是一種結構紋理或者一個獨特的物體,那麼這個區域的直方圖可以看做是一個概率函式,其表現形式是某個畫素屬於該紋理或物體的概率。而反向投影就是一種記錄給定影象中的畫素點如何適應直方圖模型畫素分佈方式的一種方法。
反向投影就是首先計算某一特徵的直方圖模型,然後使用模型去尋找影象中存在的該特徵的方法。例如,有一個顏色直方圖,可以利用反向投影在影象中找到該區域。
1.1 直方圖交叉
實現物件背景區分、複雜場景中查詢物件、不同光照條件影響等。
假設 : 模型直方圖資料, : 影象直方圖資料、直方圖交叉匹配可以被描述為如下:
其中 表示直方圖的範圍,即 bin 的個數。最終得到結果是表示多少個模型顏色畫素與影象中的畫素相同或者相似,值越大,表示越相似。歸一化表示如下:
這種方法對背景畫素變換可以保持穩定性、同時對尺度變換也有一定抗干擾作用,但是無法做到尺度不變性特徵。通過該方法可以定點陣圖像中已知物體的位置,這個方法叫做直方圖反向投影(Back Projection)。
1.2 直方圖反向投影:
查詢的方式就是不斷的在輸入影象中切割跟模板影象大小一致的影象塊,並用直方圖對比的方式與模板影象進行比較。
- 對每個直方圖bin ,直方圖的範圍 :
- 對影象每個畫素點 根據畫素值獲取對應的直方圖分佈概率
- 對得到分佈概率影象做卷積
- 求取區域性最大值,即得到已知物體位置資訊
正是因為直方圖反向投影有這樣能力,用在經典的MeanShift與CAMeanShift跟蹤演算法中來實現已知物件物體的定位。
-
假設我們有一張100x100的輸入影象,有一張10x10的模板影象,查詢的過程是這樣的:(使用單通道圖)
(1)從輸入影象的左上角(0,0)開始,切割一塊(0,0)至(10,10)的臨時影象;
(2)生成臨時影象的直方圖;
(3)用臨時影象的直方圖和模板影象的直方圖對比,對比結果記為c;
(4)直方圖對比結果c,就是結果影象(0,0)處的畫素值;
(5)切割輸入影象從(0,1)至(10,11)的臨時影象,對比直方圖,並記錄到結果影象;
(6)重複(1)~(5)步直到輸入影象的右下角。 -
注意點
輸入影象和模板影象大小: 否則可能報錯。 -
反向投影函式:
void cvCalcBackProjectPatch()
IplImage** image, 輸入影象:是一個單通道影象陣列,而非實際影象
CvArr* dst, 輸出結果:單通道32位浮點影象,寬度為W-w+1,高度為H-h+1,
其中W和H是輸入影象的寬度和高度,w和h是模板影象的寬度和高度
CvSize patch_size, 模板影象的大小:寬度和高度
CvHistogram* hist, 模板影象的直方圖:直方圖的維數和輸入影象的個數相同,並且次序要一致;
例如:輸入影象包含色調和飽和度,那麼直方圖的第0維是色調,第1維是飽和度
int method, 對比方式:跟直方圖對比中的方式類似,可以是:CORREL(相關)、
CHISQR(卡方)、INTERSECT(相交)、BHATTACHARYYA
float~factor 歸一化因子,一般都設定成1,否則很可能會出錯;這個引數的型別是double
還有最需要注意的地方:這個函式的執行效率非常的低,在使用之前尤其需要注意影象的大小,直方圖的維數,對比方式。如果說對比單個直方圖對現在的電腦來說是清風拂面,那麼反向投影是狂風海嘯。對於1010x1010的RGB輸入影象,10x10的模板影象,需要生成1百萬次3維直方圖,對比1百萬次3維直方圖。
二、彩色影象高斯反向投影
影象反向投影通常是更多物件細節資訊的彩色圖,而轉為灰度影象會導致這些細節資訊丟失、從而導致分割失敗。最常見的是基於影象直方圖特徵的反向投影。我們這裡介紹一種跟直方圖反向投影不一樣的彩色影象反向投影方法,通過基於高斯的概率分佈公式(PDF)估算,反向投影得到物件區域,該方法也可以看做最簡單的影象分割方法。缺點是物件顏色光照改變和尺度改變不具備不變性特徵。所以需要在光照度穩定情況下成像採集影象資料。 在這種情況下使用的高斯概率密度公式為:
其中:: 均值 、 :標準方差
- 輸入模型M,對M的每個畫素點(R,G,B)計算I=R+G+B r=R/I, g=G/I, b=B/I
- 根據得到權重比例值,計算得到對應的均值 與標準方差
- 對輸入影象的每個畫素點計算根據高斯公式計算P®與P(g)的乘積
- 歸一化之後輸出結果,即為最終基於高斯PDF的反向投影影象
三、演算法步驟與程式碼實現
- 首先載入模型影象與測試影象
- 根據模型影象計算得到每個通道對應的均值與標準方差引數
- 根據引數方差計算每個畫素點的PDF值
- 歸一化概率分佈圖像-即為反向投影影象,顯示
- 根據Mask得到最終顏色模型物件分割
原圖為下,roi 模型圖片為藍框區域
import cv2
import numpy as np
#roi 模型圖片
roi = cv2.imread('timg_1.png')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
#目標圖片
target = cv2.imread('timg.png')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)
#計算目標直方圖 >> 歸一化直方圖並應用反投影
roi_hist = cv2.calcHist([hsv],[0,1],None,[180,256],[0,180,0,256])
roi_normalize = cv2.normalize(roi_hist,0,255,cv2.NORM_MINMAX)
calc_Back_Project = cv2.calcBackProject([hsvt],[0,1],roi_normalize,[0,180,0,256],1)
#卷積(構建橢圓結構)
kernel_disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
filter2D_img = cv2.filter2D(calc_Back_Project,-1,kernel_disc)
#閾值二值化 >> 使用merge變成通道影象 >> 蒙板
ret,thresh = cv2.threshold(filter2D_img,50,255,0)
thresh = cv2.merge((thresh,thresh,thresh))
mask = cv2.bitwise_and(target,thresh)
#矩陣按列拼接
result = np.hstack((target,thresh,mask))
#新增文字
w,h = target.shape[:2]
font = cv2.FONT_HERSHEY_SIMPLEX
img_word0 = cv2.putText(result, "target", (10, 25), font, 0.8, (0, 0, 255), 2,)
img_word1 = cv2.putText(img_word0, "thresh", (w-30, 25), font, 0.8, (0, 0, 255), 2,)
img_result = cv2.putText(img_word1, "mask", (2*w-100, 25), font, 0.8, (0, 0, 255), 2,)
#顯示影象
cv2.imshow('img_result',img_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
import numpy as np
def read_img(roi_img,target_img):
roi = cv2.imread(roi_img)
roi = cv2.resize(roi,None,fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
hsv_roi = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
target_img = cv2.imread(target_img)
target = cv2.resize(target_img,None,fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
hsv_target = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)
return target,hsv_roi,hsv_target
def calcHist(hsv_roi,hsv_target):
#計算目標直方圖 >> 歸一化直方圖並應用反投影
roi_hist = cv2.calcHist([hsv_roi],[0,1],None,[180,256],[0,180,0,256])
roi_normalize = cv2.normalize(roi_hist,0,255,cv2.NORM_MINMAX)
calc_Back_Project = cv2.calcBackProject([hsv_target],[0,1],roi_normalize,[0,180,0,256],1)
return calc_Back_Project
def filter2D(target_img,calc_Back_Project):
kernel_disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
filter2D_img = cv2.filter2D(calc_Back_Project,-1,kernel_disc)
ret,thresh = cv2.threshold(filter2D_img,50,255,0)
thresh = cv2.merge((thresh,thresh,thresh))
mask = cv2.bitwise_and(target_img,thresh)
return thresh,mask
def Img_Outline(input_dir):
original_img = cv2.imread(input_dir)
gray_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray_img, (9, 9), 0) # 高斯模糊去噪(設定卷積核大小影響效果)
_, RedThresh = cv2.threshold(blurred, 165, 255, cv2.THRESH_BINARY) # 設定閾值165(閾值影響開閉運算效果)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 定義矩形結構元素
closed = cv2.morphologyEx(RedThresh, cv2.MORPH_CLOSE, kernel) # 閉運算(連結塊)
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel) # 開運算(去噪點)
return original_img, gray_img, RedThresh, closed, opened
def findContours_img(target_img, opened):
image, contours, hierarchy = cv2.findContours(opened, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
c = sorted(contours, key=cv2.contourArea, reverse=True)[1] # 計算最大輪廓的旋轉包圍盒
rect = cv2.minAreaRect(c) # 獲取包圍盒(中心點,寬高,旋轉角度)
box = np.int0(cv2.boxPoints(rect))
draw_img = cv2.drawContours(target_img.copy(), [box], -1, (0, 0, 255), 3)
return box,draw_img
def Add_text(target,thresh,mask):
result = np.hstack((target,thresh,mask))
w,h = target.shape[:2]
font = cv2.FONT_HERSHEY_SIMPLEX
img_word0 = cv2.putText(result, "target", (10, 25), font, 0.8, (0, 0, 255), 2,)
img_word1 = cv2.putText(img_word0, "thresh", (w-30, 25), font, 0.8, (0, 0, 255), 2,)
img_result = cv2.putText(img_word1, "mask", (2*w-100, 25), font, 0.8, (0, 0, 255), 2,)
return img_result
if __name__ =="__main__":
roi_img = "./timg_1.png"
target_img = "./timg.png"
target,hsv_roi, hsv_target = read_img(roi_img,target_img)
calc_Back_Project = calcHist(hsv_roi,hsv_target)
thresh, mask = filter2D(target_img,calc_Back_Project)
original_img, gray_img, RedThresh, closed, opened = Img_Outline(target_img)
box,draw_img = findContours_img(original_img, opened)
img_result = Add_text(target, thresh, mask)
cv2.imshow('img_result',img_result)
cv2.waitKey(0)
cv2.destroyAllWindows()