本次要整理的内容仍然是与直方图相关的操作,分别是直方图反向投影,和基于直方图反向投影的简易视频实时追踪。这两个部分的内容是息息相关的,第一部分是基础知识点,而第二部分是在此基础知识上的应用,首先开始整理直方图反向投影的内容。
- 直方图反向投影
从之前的笔记中我们已经了解到直方图是一幅图像的像素统计,那么其中必然会包含图像的某些信息,而且针对图像中的某一特定区域,其统计信息肯定在整体的直方图中也会有所体现,因此可以有这样一种想法:如果我们想从一张图像中找出一块我们感兴趣的区域,就可以先将该区域截取下来作为寻找的模板,并计算模板的直方图,最后在原图像中寻找具有该模板直方图特征的区域,那就是我们感兴趣的区域。这其实就是直方图反向投影的思想,简单来说,所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征。具体实现代码如下:
Mat model, test_image;
model = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\sample.png");
imshow("model", model);
test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\target.png");
imshow("test", test_image);
Mat model_hsv, test_image_hsv;
cvtColor(model, model_hsv, COLOR_BGR2HSV);
cvtColor(test_image, test_image_hsv, COLOR_BGR2HSV);
//计算模型的直方图
int channels[2] = { 0, 1 };
Mat hist_model;
int histSize[2] = { 32, 32 };
float range_h[2] = { 0, 180 };
float range_s[2] = { 0, 256 };
const float* HistRanges[2] = { range_h, range_s };
calcHist(&model_hsv, 1, channels, Mat(), hist_model, 2, histSize, HistRanges, true, false);
normalize(hist_model, hist_model, 0, 255, NORM_MINMAX, -1, Mat());
//通过反向投影在测试图像中找出具有模型图像直方图特征的区域
Mat backProject;
calcBackProject(&test_image_hsv, 1, channels, hist_model, backProject, HistRanges);
imshow("backProject", backProject);
在上述代码中,我们首先读取两张图像分别是:原图像和模板图像
模板图像呢是原图像中身穿紫色球衣那位球员身体的一小块区域,主要是为了将这个球衣颜色投影出来。
然后我们将这两张图像都从RGB色彩空间转换为HSV色彩空间,这是因为我们要对颜色这个特征进行投影,而RGB色彩空间对纯色的区分度不高,所以使用HSV空间的H(色调),S(饱和度)两个通道来计算,并且忽略V(亮度)通道的影响。
接着就是计算模板的直方图:
int channels[2] = { 0, 1 };
Mat hist_model;
int histSize[2] = { 32, 32 };
float range_h[2] = { 0, 180 };
float range_s[2] = { 0, 256 };
const float* HistRanges[2] = { range_h, range_s };
calcHist(&model_hsv, 1, channels, Mat(), hist_model, 2, histSize, HistRanges, true, false);
注意下这里的参数:int channels[2] = { 0, 1 }
表示对模板图像的前两个通道来计算直方图,这里同时计算了两个通道的直方图;Mat hist_model
定义存放直方图的Mat对象;int histSize[2] = { 32, 32 }
定义了两个通道各自的直方图bin区间的数量;float range_h[2] = { 0, 180 }; float range_s[2] = { 0, 256 };
分别定义两个通道各自取值范围,H通道实际上取值范围是[ 0, 360 ] ,但是在OpenCV中的HSV图像的H取值范围是[ 0, 180 ],这个是开发人员设置的,具体为什么我也没有弄清楚,猜测应该是360数值比较大超过了一个字节的表达范围吧。。?所以将其截半来进行表示;const float* HistRanges[2] = { range_h, range_s }
就是定义了一个float类型的指针数组来存放上面两个取值范围的地址。最后参数定义完成了就进行模板的直方图计算。
然后很重要的一点是,需要对模型直方图进行归一化到 [0 , 255 ],这是因为原图像中三通道值均为[0, 255], 需要将模板和投影图像的各通道取值范围归一化成相同值。
最后进行直方图反向投影操作,OpenCV提供了一个方便的API:
calcBackProject(&test_image_hsv, 1, channels, hist_model, backProject, HistRanges)
其中第一个参数是要进行投影的输入图像的地址,注意该API要求输入的是地址;
第二个参数是输入图像的数目;
第三个参数是要进行投影的通道数;
第四个参数是输入模板的直方图;
第五个参数是输出的反向投影结果,为一个Mat对象;
第六个参数是输入模板直方图的通道取值范围的地址的地址,也就是上面定义的HistRanges
;
再往后的两个参数使用默认值即可。
这样就完成了对一块球衣区域特征的提取,并反向投影到原图像中,输出反向投影结果:
由图可见,反向投影的结果就是将球衣区域给找了出来,而且输出的是一个灰度图像,如果将该图像和原图像做与(and)操作,就可以将紫色的球衣从原图中抠出来。
从结果也可以看出,直方图反向投影进行目标跟踪或目标区域提取,都是只针对目标和背景颜色差异较大的图像,如果背景中也有和目标相似的颜色,那么同样会被找出来,就会影响到输出结果。下面就将直方图反向投影运用起来,实现简单的目标跟踪。
- 基于直方图反向投影的视频实时追踪
这一部分内容需要使用到之前整理过的调用电脑摄像头的知识,如果是台式电脑没摄像头的话也可以用视频来替代,但就不能称为实时了。。。
先贴上代码:
/***********************视频实时跟踪--基于直方图反向投影*************************************/
Mat model;
model = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\ATM.jpg");
Mat model_hsv;
cvtColor(model, model_hsv, COLOR_BGR2HSV);
int channels[] = { 0,1 };
int dims = 2;
int histSize[] = { 180, 255 };
float range_h[] = { 0,180 };
float range_s[] = { 0, 255 };
const float* histRanges[] = { range_h, range_s };
Mat hist_model;
calcHist(&model_hsv, 1, channels, Mat(), hist_model, dims, histSize, histRanges);
normalize(hist_model, hist_model, 0, 255, NORM_MINMAX, -1, Mat());
Mat backProject;
VideoCapture capture;
capture.open(0);
if (!capture.isOpened())
{
cout << "调用摄像头失败" << endl;
}
Mat frame;
namedWindow("frame", WINDOW_AUTOSIZE);
Mat frame_hsv;
Mat and_image;
while (capture.read(frame))
{
flip(frame, frame, 1);
cvtColor(frame, frame_hsv, COLOR_BGR2HSV);
calcBackProject(&frame_hsv, 1, channels, hist_model, backProject, histRanges);
//bitwise_and(frame, frame, and_image, backProject);
imshow("frame", backProject);
char ch = waitKey(20);
if (ch == 27)
{
break;
}
}
capture.release();
其实这个代码十分的简陋,只是将一个感兴趣的目标区域作为了模板,然后再实时视频流中进行直方图反向投影,最后输出显示反向投影的结果图,仅此而已。但是也能将视频流中出现的目标实时的投影出来,只不过是显示黑白图像,如果想要抠出目标区域,同样可以尝试通过图像的逻辑操作来实现。由于视频不好上传,就只能截图勉强看一看效果。
emmmmm由图可见,可视化效果真的不咋地,但是从这个奥特曼的清秀的依稀可辨的轮廓可以看出,还是能将这个目标物体投影出来的,并且在实时视频流中,一直移动该目标物体的话也能够将其实时的位置投影出来。所以大体上能够实现一个实时追踪目标的功能,只是丑,纯粹的丑,而且这种实现方式的鲁棒性其实也还是偏低的,只适用于没事做着玩。。。
好啦这次整理到此结束,下次继续~
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!
来源:CSDN
作者:邱小兵
链接:https://blog.csdn.net/weixin_45224869/article/details/104588105