在介绍CAMShift算法之前,我们先了解一下meanshift(均值漂移)算法。

meanshift是一种是基于核密度估计的爬山算法,可用于聚类、图像分割、跟踪等。
视频分析与对象跟踪(四)(CAMShift)
(1)均值漂移的基本形式

给定d维空间的n个数据点集X,那么对于空间中的任意点x的mean shift向量基本形式可以表示为:
视频分析与对象跟踪(四)(CAMShift)
这个向量就是漂移向量,其中Sk表示的是数据集的点到x的距离小于球半径h的数据点。也就是:
视频分析与对象跟踪(四)(CAMShift)
而漂移的过程,说的简单一点,就是通过计算得漂移向量,然后把球圆心x的位置更新一下,更新公式为:视频分析与对象跟踪(四)(CAMShift)
其中Mh为偏移量。
总结为一句话就是:求解一个向量,使得圆心一直往数据集密度最大的方向移动。说的再简单一点,就是每次迭代的时候,都是找到圆里面点的平均位置作为新的圆心位置。
(2)加入核函数的漂移向量

这个说的简单一点就是加入一个高斯权重,最后的漂移向量计算公式为:
视频分析与对象跟踪(四)(CAMShift)
因此每次更新的圆心坐标为:
视频分析与对象跟踪(四)(CAMShift)
在原图中选定roi区域,可以得到这个roi区域的直方图,和原图像的直方图比较找到最相近的直方图区域,最后直方图反向映射,实现定位。同样的选取第二帧的图像中与roi区域最近的直方图区域(直方图取值范围相同,且需要通过归一化来避免光照带来的影响),反向映射到选取的第二帧图像中,实现第二帧图像中roi区域的定位。
视频分析与对象跟踪(四)(CAMShift)

Meanshift算法缺点,选定的rio区域的大小是不能改变的,但是图像的形状或者大小已经改变了,定位不准确,所以引入了CAMShift(持续自适应Meanshift算法)。

CAMShift可以满足窗口尺寸自适应变化及适合变形目标检测。
因为RGB图像要共有5维(考虑了变量x,y),这里用HSV空间的H来代替。其具体转化见下图:
视频分析与对象跟踪(四)(CAMShift)
流程图:
视频分析与对象跟踪(四)(CAMShift)
代码实现(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;
	}

视频分析与对象跟踪(四)(CAMShift)
视频分析与对象跟踪(四)(CAMShift)

相关文章: