在计算机视觉这一块,图像反向投影的最终目的是获取ROI然后实现对ROI区域的标注、识别、测量等图像处理与分析,是计算机视觉与人工智能的常见方法之一。 如果一幅图像的区域中显示的是一种结构纹理或者一个独特的物体,那么这个区域的直方图可以看做是一个概率函数,其表现形式是某个像素属于该纹理或物体的概率。而反向投影就是一种记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法。
反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征的方法。例如,有一个颜色直方图,可以利用反向投影在图像中找到该区域。
实现对象背景区分、复杂场景中查找对象、不同光照条件影响等。
假设 M M M : 模型直方图数据, I I I : 图像直方图数据、直方图交叉匹配可以被描述为如下:
∑ j = 0 n min ( I j , M j ) \sum_{j=0}^n \min(I_j,M_j) j=0∑nmin(Ij,Mj)
其中 J J J 表示直方图的范围,即 bin 的个数。最终得到结果是表示多少个模型颜色像素与图像中的像素相同或者相似,值越大,表示越相似。归一化表示如下:
H ( I , M ) = ∑ j = 0 n min ( I j , M j ) ∑ j = 1 n M j H(I,M) = \frac{\sum_{j=0}^n \min(I_j,M_j)}{\sum_{j=1}^n M_j} H(I,M)=∑j=1nMj∑j=0nmin(Ij,Mj)
这种方法对背景像素变换可以保持稳定性、同时对尺度变换也有一定抗干扰作用,但是无法做到尺度不变性特征。通过该方法可以定位图像中已知物体的位置,这个方法叫做直方图反向投影(Back Projection)。
查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。
正是因为直方图反向投影有这样能力,用在经典的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)步直到输入图像的右下角。
注意点
输入图像和模板图像大小: s i z e o r i g i n a l _ i m g ⩾ s i z e m o d e l _ i m g \rm size_{original\_img} \geqslant \rm size_{model\_img} sizeoriginal_img⩾sizemodel_img 否则可能报错。
反向投影函数:
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)估算,反向投影得到对象区域,该方法也可以看做最简单的图像分割方法。缺点是对象颜色光照改变和尺度改变不具备不变性特征。所以需要在光照度稳定情况下成像采集图像数据。 在这种情况下使用的高斯概率密度公式为:
P ( r ) = 1 σ r 2 π exp { − ( r − μ ) 2 ) 2 σ r 2 } P(r) = \frac{1}{\sigma_r \sqrt{2\pi}}\exp\left \{ -\frac{(r-\mu)^2)}{2\sigma_r^2 } \right \} P(r)=σr2π1exp{−2σr2(r−μ)2)}
其中: μ \mu μ: 均值 、 σ \sigma σ :标准方差
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()
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
def cv2ImgAddText(img, text, left, top, textColor=(0, 255, 0), textSize=20):
if (isinstance(img, np.ndarray)):
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
fontStyle = ImageFont.truetype("font/simsun.ttc", textSize, encoding="utf-8")
draw.text((left, top), text, textColor, font=fontStyle)
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
if __name__ == '__main__':
img = cv2.imread('./hua.png')
add = cv2ImgAddText(img,'这是什么花', 12, 12, (255, 0, 0),20)
add = cv2ImgAddText(add, '这是画的葫芦花', 12, 42, (255, 0, 0),25)
cv2.imwrite('./hua2.png',add)
Python: cv.FONT_HERSHEY_PLAIN
small size sans-serif font
FONT_HERSHEY_DUPLEX
Python: cv.FONT_HERSHEY_DUPLEX
normal size sans-serif font (more complex than FONT_HERSHEY_SIMPLEX)
FONT_HERSHEY_COMPLEX
Python: cv.FONT_HERSHEY_COMPLEX
normal size serif font
FONT_HERSHEY_TRIPLEX
Python: cv.FONT_HERSHEY_TRIPLEX
normal size serif font (more complex than FONT_HERSHEY_COMPLEX)
FONT_HERSHEY_COMPLEX_SMALL
Python: cv.FONT_HERSHEY_COMPLEX_SMALL
smaller version of FONT_HERSHEY_COMPLEX
FONT_HERSHEY_SCRIPT_SIMPLEX
Python: cv.FONT_HERSHEY_SCRIPT_SIMPLEX
hand-writing style font
FONT_HERSHEY_SCRIPT_COMPLEX
Python: cv.FONT_HERSHEY_SCRIPT_COMPLEX
more complex variant of FONT_HERSHEY_SCRIPT_SIMPLEX
FONT_ITALIC
Python: cv.FONT_ITALIC
flag for italic font
字体大小:数值越大,字体越大
字体粗细:越大越粗,数值表示线占有直径像素个数
鸣谢
https://mp.weixin.qq.com/s?__biz=MzA4MDExMDEyMw==&mid=2247483990&idx=1&sn=95d61a4a005e349bbb4022f9f3a95a29&chksm=9fa87512a8dffc04a4ef0126404c88e494f37dd78ed6a8561a6d7f675d015548a69c6dd471e1&scene=21#wechat_redirect