在介绍CAMShift算法之前,我们先了解一下meanshift(均值漂移)算法。
meanshift是一种是基于核密度估计的爬山算法,可用于聚类、图像分割、跟踪等。
(1)均值漂移的基本形式
给定d维空间的n个数据点集X,那么对于空间中的任意点x的mean shift向量基本形式可以表示为:
这个向量就是漂移向量,其中Sk表示的是数据集的点到x的距离小于球半径h的数据点。也就是:
而漂移的过程,说的简单一点,就是通过计算得漂移向量,然后把球圆心x的位置更新一下,更新公式为:
其中Mh为偏移量。
总结为一句话就是:求解一个向量,使得圆心一直往数据集密度最大的方向移动。说的再简单一点,就是每次迭代的时候,都是找到圆里面点的平均位置作为新的圆心位置。
(2)加入核函数的漂移向量
这个说的简单一点就是加入一个高斯权重,最后的漂移向量计算公式为:
因此每次更新的圆心坐标为:
在原图中选定roi区域,可以得到这个roi区域的直方图,和原图像的直方图比较找到最相近的直方图区域,最后直方图反向映射,实现定位。同样的选取第二帧的图像中与roi区域最近的直方图区域(直方图取值范围相同,且需要通过归一化来避免光照带来的影响),反向映射到选取的第二帧图像中,实现第二帧图像中roi区域的定位。
Meanshift算法缺点,选定的rio区域的大小是不能改变的,但是图像的形状或者大小已经改变了,定位不准确,所以引入了CAMShift(持续自适应Meanshift算法)。
CAMShift可以满足窗口尺寸自适应变化及适合变形目标检测。
因为RGB图像要共有5维(考虑了变量x,y),这里用HSV空间的H来代替。其具体转化见下图:
流程图:
代码实现(opencv+c++):
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int smin = 15;
int vmin = 40;
int vmax = 256;
int bins = 16;
int main(int argc, char** argv) {
VideoCapture capture;
capture.open("D:/picture/opencv/images/video_003.avi");
if (!capture.isOpened()) {
printf("could not find video data file...\n");
return -1;
}
namedWindow("CAMShift Tracking", CV_WINDOW_AUTOSIZE);
namedWindow("ROI Histogram", CV_WINDOW_AUTOSIZE);
bool firstRead = true;
float hrange[] = { 0, 180 };
const float* hranges = hrange;
Rect selection;
Mat frame, hsv, hue, mask, hist, backprojection;
Mat drawImg = Mat::zeros(300, 300, CV_8UC3);
while (capture.read(frame)) {
if (firstRead) {
Rect2d first = selectROI("CAMShift Tracking", frame);
selection.x = first.x;
selection.y = first.y;
selection.width = first.width;
selection.height = first.height;
printf("ROI.x= %d, ROI.y= %d, width = %d, height= %d", selection.x, selection.y, selection.width, selection.height);
}
// convert to HSV
cvtColor(frame, hsv, COLOR_BGR2HSV);
inRange(hsv, Scalar(0, smin, vmin), Scalar(180, vmax, vmax), mask);
hue = Mat(hsv.size(), hsv.depth());
int channels[] = { 0, 0 };
mixChannels(&hsv, 1, &hue, 1, channels, 1);
if (firstRead) {
// ROI 直方图计算
Mat roi(hue, selection);
Mat maskroi(mask, selection);
calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &hranges);
normalize(hist, hist, 0, 255, NORM_MINMAX);
// show histogram
int binw = drawImg.cols / bins;
Mat colorIndex = Mat(1, bins, CV_8UC3);
for (int i = 0; i < bins; i++) {
colorIndex.at<Vec3b>(0, i) = Vec3b(saturate_cast<uchar>(i * 180 / bins), 255, 255);
}
cvtColor(colorIndex, colorIndex, COLOR_HSV2BGR);
for (int i = 0; i < bins; i++) {
int val = saturate_cast<int>(hist.at<float>(i)*drawImg.rows / 255);
rectangle(drawImg, Point(i*binw, drawImg.rows), Point((i + 1)*binw, drawImg.rows - val), Scalar(colorIndex.at<Vec3b>(0, i)), -1, 8, 0);
}
}
// back projection
calcBackProject(&hue, 1, 0, hist, backprojection, &hranges);
// CAMShift tracking
backprojection &= mask;
RotatedRect trackBox = CamShift(backprojection, selection, TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1));
// draw location on frame;
ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8);
if (firstRead) {
firstRead = false;
}
imshow("CAMShift Tracking", frame);
imshow("ROI Histogram", drawImg);
char c = waitKey(50);// ESC
if (c == 27) {
break;
}
}
capture.release();
waitKey(0);
return 0;
}