这里说的是OpenCV中实现的Meanshift算法的大体概念。
在OpenCV中meanshift算法的原理,大体上是这样的:
首先,预先定义一个窗口(可以通过openCv中的ROI在图像上定义一个感兴趣的窗口),然后计算窗口内所有像素(数据)点的重心,然后将窗口中的中心移动到重心点。重复这个过程,直到满足迭代终止条件。
OpenCV2中,实现meanshift算法的函数是:
cv::meanShift(参数,参数,参数)
当然,调用该函数前要进行一些设置,获得符合函数要求的参数(下面会详细讲)
Meanshift查找相似物体的流程
1、在图片1在定义感兴趣区域(ROI),获得感兴趣的物体A。
2、对ROI区域,提取合适的特征(这里提取归一化后的直方图特征)。
3、读取包含与物体A相似物体的图片2,通过上面提取出的ROI区域的直方图特征。对图片2进行直方图反投影,获得一张“图片2“关于”ROI区域“的概率图。
直方图特征反投影:简单起见,下面以灰度图(彩色图与其类似)大体来说,直方图归一化后,可以将直方图看成一个概率函数。例如,假设在归一化后的直方图中,灰度值为60的,对应的值为0.4,而所有灰度值对应的值,加起来为1。这样,便成为了一个概率函数 yi=f(xi), i=0…255,y1+y2+…y255=1.现在,对于一张新的图片,将它的每个像素点,用上面归一化后的直方图去映射成新的值,这个值可以说成是该像素点属于直方图的概率(如,图片中像素的灰度值为60,那么将其变为0.4)。这个过程,就叫对某一张图片,用某个直方图特征反投影,投影后得到的图片,可以叫作对于该直方图特征的概率图。
概率图有什么用?
仔细想想,一个直方图特征对应的一张图像。用这个直方图特征在某张图像在投影,计算出来的概率图,就是该图像每一个像素点与直方图特征对应的图像的相似度。
4、获得一张“图片2“关于”ROI区域“的概率图后,在概率图中与定义一块与ROI区域位置、大小一张的区域,以这个区域为起始点,通过Meanshift算法迭代,找到概率图中,概率最大的区域。该区域(位置),即为图片2中与图片1感兴趣区域(ROI)最相似部分的位置。
histogram.h
//Histogram类,计算彩色图像的灰度值
#ifndef HISTOGRAM
#define HISTOGRAM
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui\highgui.hpp>
class Histogram {
private:
int histSize[3];
float hranges[2];
const float* ranges[3];
int channels[3];
public:
Histogram() {
histSize[0]= histSize[1]= histSize[2]= 256;
hranges[0]= 0.0; // 灰度值区域0到255
hranges[1]= 255.0;
ranges[0]= hranges; //三个通道的灰度值的范围
ranges[1]= hranges;
ranges[2]= hranges;
channels[0]= 0; // 三个通道
channels[1]= 1;
channels[2]= 2;
}
//计算机hsv图像色调通道直方图,并去除低饱和的像素点
cv::MatND getHueHistogram(const cv::Mat &image, int minSaturation = 0)
{
//直方图
cv::MatND hist;
//HSV空间
cv::Mat hsv;
//转换到HSV空间
cv::cvtColor(image, hsv, CV_BGR2HSV);
//掩码,只处理非零点
cv::Mat mask;
//剔除低于设置饱和度的点
if (minSaturation > 0)
{
std::vector<cv::Mat>v;
cv::split(hsv, v);
cv::threshold(v[1], mask, minSaturation, 255, cv::THRESH_BINARY);
}
//色调值的范围
hranges[0] = 0.0;
hranges[1] = 180.0;
//只处理0通道
channels[0] = 0;
//计算直方图
cv::calcHist(&hsv,
1,
channels,
mask,
hist,
1,
histSize,
ranges
);
return hist;
}
};
#endif
contentFinder.h
#ifndef OFINDER
#define OFINDER
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui\highgui.hpp>
class ContentFinder {
private:
float hranges[2];//像素值的范围
const float* ranges[3];//指向三个通道像素值范围的指针
int channels[3];//通道
float threshold;//阈值
cv::MatND histogram;//直方图
public:
//初始化
ContentFinder() : threshold(-1.0f){
ranges[0]= hranges; // 所有通道有相同的范围
ranges[1]= hranges;
ranges[2]= hranges;
}
// 设置阈值[0~1]
void setThreshold(float t) {
threshold= t;
}
// 得到阈值
float getThreshold() {
return threshold;
}
// 设置直方图
void setHistogram(const cv::MatND& h) {
histogram= h;
//直方图归一化
cv::normalize(histogram,histogram,1.0);
}
// 反投影直方图
cv::Mat find(const cv::Mat& image, float minValue, float maxValue, int *channels, int dim) {
cv::Mat result;
hranges[0]= minValue;
hranges[1]= maxValue;
for (int i=0; i<dim; i++)
this->channels[i]= channels[i];
cv::calcBackProject(&image,
1, //1张图片
this->channels, //通道
histogram, // 直方图
result, // 结果
ranges, // 每个维度的灰度值范围
255.0 // 缩放因子
);
// 阈值化
if (threshold>0.0)
cv::threshold(result, result, 255*threshold, 255, cv::THRESH_BINARY);
return result;
}
};
#endif
源.cpp
//不显示CMD窗口,或者关闭CMD窗口,程序不退出
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#include"histogram.h"
#include"contentFinder.h"
//需要包含此文件才能调用Meanshift函数
#include<opencv2\video\tracking.hpp>
using namespace cv;
int main()
{
//读取第一张图片
Mat image = imread("0001.jpg");
//定义感兴趣区域
Mat imaRoi = image(Rect(213, 121, 21, 95));
//设置最小饱和度
int minSat = 65;
Histogram h;
//获得imaRoi的直方图特征
MatND hist = h.getHueHistogram(imaRoi, minSat);
ContentFinder finder;
finder.setHistogram(hist);
//读取第二张图片
Mat findimage = imread("0024.jpg");
Mat hsv;
//将第二张图片转换为HSV格式
cvtColor(findimage, hsv, CV_BGR2HSV);
std::vector<Mat>v;
split(hsv, v);
//将低于 最小饱和度的像素点 设置为0
cv::threshold(v[1], v[1], minSat, 255, THRESH_BINARY);
int ch[1] = { 0 };
cv::Mat result = finder.find(hsv, 0.0f, 180.0f, ch, 1);
//剔除低饱和的点
bitwise_and(result, v[1], result);
//在第一张图片上画出感兴趣区域的位置
rectangle(image, Rect(213, 121, 21, 95), Scalar(255, 0, 0));
//设置迭代停止条件
TermCriteria criteria(TermCriteria::MAX_ITER, 100, 0.01);
//预定义初始矩形区域
Rect rect(213, 121, 21, 95);
//调用meanshift算法,最终得到rect,相似区域的位置
meanShift(result, rect, criteria);
//在第二张图片上,显示相似区域的位置
rectangle(findimage, rect, Scalar(255, 0, 0));
imshow("第一张", image);
imshow("第二张", findimage);
cv::waitKey(0);
}
最后,可以对连续的视频序列中,当前帧的某一个目标,通过某种方法得到下一帧图像对于该目标的相似度判别数据,然后应用Meanshift算法实现目标的跟踪。(博主本人的主要研究方向就是目标跟踪~~)