假设我们希望从图2(待检测图像)中检测出“飞行的蜥蜴”。首先要做的就是准备一个样本,比如在图1(参考图像)中选择一个包含样本的兴趣区域。
图1(参考图像):
图2(待检测图像,可注意到飞蜥位置和形态都出现了轻微改变):
我们计算参考图像中兴趣区域的直方图(这里取H(色调)通道),然后将直方图归一化。这时直方图的箱子值代表的是特定颜色出现的概率。
反向投影直方图的过程包括:从归一化的直方图中读取概率值并把待检测图像中的每个像素替换成与之对应的概率值。
OpenCV中的反向投影函数:
//Calculates the back projection of a histogram.
void calcBackProject(const Mat* images,
int nimages,//图像个数(这里选1)
const int* channels, //用到的通道数,取决于直方图维数
InputArray hist,//输入直方图
OutputArray backProject,//反向投影结果
const float** ranges, //iamges图像值的范围
double scale=1,
bool uniform=true )
反向投影的结果就是概率分布图,表示一个指定图像片段出现在特定位置的概率,属于该位置的概率从亮(高概率)到暗(低概率):
假设我们已经知道图像中某个物体的大概位置(视频跟踪中相邻帧目标位置改变不大),就可以用概率分布图找到物体的准确位置。最可能出现的位置就是窗口中概率最大的位置。因此我们可以从一个初始位置开始,在周围反复移动,就可能找到物体所在的准确位置。这种算法称为mean shift。
OpenCV中的meanshift函数用法:
//设置迭代停止条件
cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER,10,0.01);
//meanshift算法实现,运行后rect的值会更新
cv::meanShift(result,rect,criteria);
最终目标跟踪的结果:
完整代码:
反向投影直方图的取得,封装在了ContentFinder类中
//头文件 ContentFinder.h
#pragma once
#include"opencv2/opencv.hpp"
#include"opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
class ContentFinder
{
private:
//直方图参数
float hranges[2];
const float* ranges[3];
int channels[3];
float threshold;
cv::Mat histogram;
public:
ContentFinder();
void setThreshold(float t);//阈值化参数。t>0,将反向投影结果阈值化,t<=0则不阈值化
void setHistogram(cv::Mat hist);//输入要反投影的直方图
cv::Mat find(const cv::Mat& image);//返回反投影结果
cv::Mat find(const cv::Mat& image, float minValue, float maxValue, int* channels);//重载
~ContentFinder();
};
//实现文件 ContentFinder.cpp
#include "ContentFinder.h"
ContentFinder::ContentFinder() :threshold(0.1f)
{
ranges[0] = hranges;
ranges[1] = hranges;
ranges[2] = hranges;
}
void ContentFinder::setThreshold(float t)
{
threshold = t;
}
void ContentFinder::setHistogram(cv::Mat hist)
{
histogram = hist;
cv::normalize(histogram,histogram,1.0);
}
cv::Mat ContentFinder::find(const cv::Mat& image)
{
hranges[0] = 0.0;
hranges[1] = 256.0;
channels[0] = 0;
channels[1] = 1;
channels[2] = 2;
return find(image,hranges[0],hranges[1],channels);
}
cv::Mat ContentFinder::find(const cv::Mat& image, float minValue, float maxValue, int* channels)
{
cv::Mat result;
hranges[0] = minValue;
hranges[1] = maxValue;
for (int i = 0; i < histogram.dims;i++)
{
this->channels[i] = channels[i];
}
//反投影直方图
cv::calcBackProject(&image,1,channels,histogram,result,ranges,255.0);
//阈值化
if (threshold > 0)
{
cv::threshold(result,result,255.0*threshold,255.0,cv::THRESH_BINARY);
}
return result;
}
ContentFinder::~ContentFinder()
{
}
main函数里的代码:
//main.cpp
cv::Mat image = cv::imread("001.jpg", 1);//参考图像
cv::Mat hsv;
cv::cvtColor(image,hsv,CV_BGR2HSV);
cv::Rect rect(230, 210, 130, 140);//采样区域
cv::rectangle(image, rect, cv::Scalar(0, 255, 0));//初始位置,为绿框
cv::imshow("image", image);
cv::Mat imageROI = image(rect);
int minSat = 65;
ColorHistogram hc;
hc.setSize(8);
cv::Mat colorHist = hc.getHueHistogram(imageROI,minSat);
//在第二幅图中检测
cv::Mat image2 = cv::imread("002.jpg", 1);
cv::Mat hsv2;
cv::cvtColor(image2, hsv2, CV_BGR2HSV);
ContentFinder finder;
finder.setHistogram(colorHist);
finder.setThreshold(-1.0f);//不设阈值
int ch[1] = {0};
cv::Mat result = finder.find(hsv2,0.0f,180.0f,ch);//反投影结果
cv::imshow("finder", result);
cv::rectangle(image2, rect, cv::Scalar(0, 255, 0));//初始位置,为绿框
cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER,10,0.01);
cv::meanShift(result,rect,criteria);
cv::rectangle(image2, rect, cv::Scalar(0, 0, 255));//跟踪位置,为红框
cv::imshow("draw rectangle", image2);
代码中调用了彩色图像直方图计算类的方法。彩色图像直方图的计算,参见另一篇博客:OpenCV计算和显示图像直方图