OpenCV—python 反向投影 ROI

文章目录

      • 一、反向投影概念
          • 1.1 直方图交叉
          • 1.2 直方图反向投影:
      • 二、彩色图像高斯反向投影
      • 三、算法步骤与代码实现
          • 附加:添加中文文本

一、反向投影概念

在计算机视觉这一块,图像反向投影的最终目的是获取ROI然后实现对ROI区域的标注、识别、测量等图像处理与分析,是计算机视觉与人工智能的常见方法之一。 如果一幅图像的区域中显示的是一种结构纹理或者一个独特的物体,那么这个区域的直方图可以看做是一个概率函数,其表现形式是某个像素属于该纹理或物体的概率。而反向投影就是一种记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法。

反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征的方法。例如,有一个颜色直方图,可以利用反向投影在图像中找到该区域。

1.1 直方图交叉

实现对象背景区分、复杂场景中查找对象、不同光照条件影响等。
假设 M M M : 模型直方图数据, I I I : 图像直方图数据、直方图交叉匹配可以被描述为如下:
∑ j = 0 n min ⁡ ( I j , M j ) \sum_{j=0}^n \min(I_j,M_j) j=0nmin(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=1nMjj=0nmin(Ij,Mj)
这种方法对背景像素变换可以保持稳定性、同时对尺度变换也有一定抗干扰作用,但是无法做到尺度不变性特征。通过该方法可以定位图像中已知物体的位置,这个方法叫做直方图反向投影(Back Projection)

1.2 直方图反向投影:

查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。

  1. 对每个直方图bin ,直方图的范围 J J J R j = M j I R_j = \frac{M_j}{I} Rj=IMj
  2. 对图像每个像素点 I ( x , y ) I(x,y) I(x,y) 根据像素值获取对应的直方图分布概率 b ( x , y ) = min ⁡ ( R h ( x , y ) , I ) \rm b_{(x,y)} = \min (R_{h(x,y)},I) b(x,y)=min(Rh(x,y),I)
  3. 对得到分布概率图像做卷积
  4. 求取局部最大值,即得到已知物体位置信息

正是因为直方图反向投影有这样能力,用在经典的MeanShift与CAMeanShift跟踪算法中来实现已知对象物体的定位。

  1. 假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:(使用单通道图)
    (1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
    (2)生成临时图像的直方图;
    (3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
    (4)直方图对比结果c,就是结果图像(0,0)处的像素值;
    (5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
    (6)重复(1)~(5)步直到输入图像的右下角。

  2. 注意点
    输入图像和模板图像大小: 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_imgsizemodel_img 否则可能报错。

  3. 反向投影函数:

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 σ :标准方差

  1. 输入模型M,对M的每个像素点(R,G,B)计算I=R+G+B r=R/I, g=G/I, b=B/I
  2. 根据得到权重比例值,计算得到对应的均值 与标准方差
  3. 对输入图像的每个像素点计算根据高斯公式计算P®与P(g)的乘积
  4. 归一化之后输出结果,即为最终基于高斯PDF的反向投影图像

三、算法步骤与代码实现

  1. 首先加载模型图像与测试图像
  2. 根据模型图像计算得到每个通道对应的均值与标准方差参数
  3. 根据参数方差计算每个像素点的PDF值
  4. 归一化概率分布图像-即为反向投影图像,显示
  5. 根据Mask得到最终颜色模型对象分割

原图为下,roi 模型图片为蓝框区域
OpenCV—python 反向投影 ROI_第1张图片

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()

OpenCV—python 反向投影 ROI_第2张图片

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)

OpenCV—python 反向投影 ROI_第3张图片
字体:

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

你可能感兴趣的:(OpenCV,计算机视觉)