当前使用版本opencv4.3
- CAMShift(ContinuouslyAdaptive Mean-Shift)持续自适应均值位移
- Mean-Shift分割算法:找到空间上颜色分布的峰值
- 选择特征模型 - 选择转换为特征空间(直方图量化特征) - 特征空间PDF模型数据
- CAMShift(窗口尺寸自动变化、适合变形目标检测)
- 流程:第一帧、读取第一帧、选择ROI区域、HSV空间H通道直方图表达、直方图反向投影、CAMShift、绘制位置更新显示
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//HSV
int smin = 30;
int smax = 255;
int vmin = 40;
int vmax = 255;
//calcHist参数
int histSize = 16;//区间的个数
float hue_ranges[] = { 0,180 };
const float* ranges = hue_ranges;
//https://blog.csdn.net/kilotwo/article/details/89053311
int main(int argc, char** argv)
{
VideoCapture capture;
//capture.open(0);
capture.open("../path.mp4");
if (!capture.isOpened())
{
cout << "could not load video..." << endl;
return -1;
}
bool firstRead = true;//用于判断是否第一次读取视频
Rect selection;//selectROI用鼠标选择的ROI区域
Mat frame,hsv,mask,hue, temp_histogram, backprojection;
Mat Histogram = Mat::zeros(300,300,CV_8UC3);
namedWindow("CAMShift Tracking", WINDOW_AUTOSIZE);
while (capture.read(frame))
{
if (firstRead)
{
//获取第一帧的ROI区域
Rect2d first_img = selectROI("CAMShift Tracking", frame);//在"CAMShift Tracking"窗口的frame图像上用鼠标选择ROI区域
selection.x = first_img.x;
selection.y = first_img.y;
selection.width = first_img.width;
selection.height = first_img.height;
cout << "ROI的x值为: " << selection.x << endl;
cout << "ROI的y值为: " << selection.y << endl;
cout << "ROI的width值为: " << selection.width << endl;
cout << "ROI的height值为: " << selection.height << endl;
}
//转换到HSV空间
cvtColor(frame, hsv, COLOR_BGR2HSV);
//测试矩阵frame的元素是否在其他两个矩阵的值之间//提取黄色
inRange(hsv, Scalar(0, smin, vmin), Scalar(180, smax, vmax), mask);
hue = Mat(hsv.size(), hsv.depth());
int channels[] = { 0,0 };//fromto映射//指定被复制通道与要复制到的位置组成的索引对
size_t npairs = 1;指定被复制通道与要复制到的位置channels[]组成的索引对
mixChannels(&hsv, 1, &hue, 1, channels, npairs); //输入矩阵通道 重新排列到 输出矩阵通道(HSV中的H值)
if (firstRead)
{
//计算ROI直方图
Mat ROI(hue, selection);//Mat& image , Rect& roi
Mat MaskROI(mask, selection);
calcHist(&ROI, 1, 0, MaskROI, temp_histogram, 1, &histSize, &ranges);//计算直方图
normalize(temp_histogram, temp_histogram, 0, 255, NORM_MINMAX);//归一化
//显示直方图图像
int bins = Histogram.cols / histSize; //300/16 = 18.5 单个区间宽度
//定义一个缓冲单bins矩阵,1行16列,用于存放颜色数据,用于直方图histSize个bin的“染色”
Mat ColorIndex = Mat(1, histSize, CV_8UC3);//1行histSize=16列
for (int i = 0; i < histSize; i++)
{
ColorIndex.at<Vec3b>(0, i) = Vec3b(saturate_cast<uchar>(i * 180 / histSize), 255, 255);//只是为了好看
}
cvtColor(ColorIndex, ColorIndex, COLOR_HSV2BGR);
for (int j = 0; j < histSize; j++)
{
//value是直方图temp_histogram的相对Histogram的高度
//temp_histogram.at(i)获取了第i个bin直方图数据,除以255后得到百分比
//再乘以Histogram的行数就得到了相对高度,最后进行int的强制类型转换,转换为整数。
int value = saturate_cast<int>(temp_histogram.at<float>(j)*Histogram.rows / 255);
//之后使用rectangle()函数进行16个bin的绘制
//值得注意的是矩阵的坐标系以左上角为原点,y轴是向下的
//而需要展示给人看的直方图图案是左下角为原点,y轴向上的
//因此rectangle的两个标定点的纵坐标是Histogram.rows和(Histogram.rows - val)而不是0和val
rectangle(Histogram,
Point( j*bins, Histogram.rows), //矩阵对角点:左下角
Point((j + 1)*bins, Histogram.rows - value), //矩阵对角点:右上角
Scalar(ColorIndex.at<Vec3b>(0, j)), -1, 8, 0);
}
firstRead = false;//需要绘制第一帧的直方图
}
//直方图反向投影
/*void calcBackProject(
const Mat* images, //输入的数组
int nimages, //输入数组的个数
const int* channels, //需要统计的通道索引
InputArray hist, //输入的直方图
OutputArray backProject,//目标的反向投影
const float** ranges, //每一位数值的取值范围
double scale=1, //输出方向投影的缩放因子
bool uniform=true //指示直方图是否均匀的标识符
)
*/
//用来计算像素和直方图模型中像素吻合度的方法
calcBackProject(&hue, 1, 0, temp_histogram, backprojection, &ranges);
backprojection &= mask;//a&=b 即 a=a&b 其中&为位与运算//给b取了一个别名叫a,所有对b的操作都是直接作用于a
//CAMShift
RotatedRect trackBox = CamShift(
backprojection, //反向投影
selection,//矩形搜索框
TermCriteria((TermCriteria::COUNT | TermCriteria::EPS),10,1));//迭代中止条件
//绘制位置更新显示在frame上
//获取CamShift的返回值,是一个旋转矩形,根据旋转矩形绘制一个椭圆形显示在图像上作为追踪结果。
ellipse(frame, trackBox, Scalar(255, 0, 255), 3, 8);
if (firstRead)
{
firstRead = false;
}
imshow("CAMShift Tracking", frame);
imshow("Histogram", Histogram);
char c = waitKey(100);
if (c == 27)
{
break;
}
}
capture.release();
waitKey(0);
return 0;
}