当前位置: 首页 > news >正文

OpenCV图像与视频分析笔记 — 视频部分

文章目录

  • 前言
  • 视频部分
    • 1. 视频流的基本操作
    • 2. 图像的色彩空间实操
    • 3. 直方图反向投影
    • 4. Harris角点检测
    • 5. Shi-Tomas角点检测
    • 6. 利用Image Watch调试程序,进行视频帧分析
    • 7. 视频帧背景分析(通过背景减去器,提取前景的ROI区域)
    • 8. 基于光流法的视频分析 - 上
    • 9. 基于光流法的视频分析 - 下
    • 10. 均值迁移分析法
  • 总结


前言

  接文章OpenCV图像与视频分析笔记 — 图像部分,本文章是视频部分的笔记。包括了视频流的基本操作、角点检测、直方图反向投影、背景替换和光流分析等内容
  本文章根据上述视频部分的内容,详细解释和例举OpenCV4提供的常见10个API是如何进行常见的视频流分析。


视频部分

1. 视频流的基本操作

  • 视频流的读写操作
  • 获取视频流基本信息
/*视频流读操作函数原型1explicit修饰的构造函数不允许隐式类型转换,只能使用构造函数,如VideoCapture capture = 0是非法的,如果没有explicit修饰,就是合法的index: 表示打开的摄像头索引号,0表示打开默认的摄像头apiPreference: 指定了首选的捕获API,CAP_ANY是一个宏,表示让OpenCV选择最适合的API。如果需要指定的API,则可以通过传递相应的宏来实现*/
explicit VideoCapture(int index, int apiPreference = CAP_ANY);/*视频流读操作函数原型2filename: 表示需要读取的视频流文件绝对路径其余参数和上述类似*/
explicit VideoCapture(const String& filename, int apiPreference = CAP_ANY);/*视频流写操作函数原型filename:视频流写的文件绝对路径fourcc:视频流编解码算法,four character code,四字符代码,一个标识符,比如写入MP4类型的文件,可以使用VideoWriter::fourcc('m', 'p', '4', 'v')fps:视频流写入的帧数frameSize:视频流写入的每一帧大小isColor:默认true表示彩色帧,false表示灰度帧*/
VideoWriter(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);/*获取视频流基本信息,openCV中的VideoCapture对象提供了get函数,用来获取视频流相关的信息
*/
Videocapture capture2("D:\\Photo\\rheed_video\\1.mp4");//获取视频流每一秒的帧数
int fps = capture2.get(CAP_PROP_FPS);
//FOURCC: four character code,四字符代码,表示视频压缩算法类型的一种
int type = capture2.get(CAP_PROP_FOURCC);	
//获取视频流每一帧的宽度
int width = capture2.get(CAP_PROP_FRAME_WIDTH);
//获取视频流每一帧的高度
int height = capture2.get(CAP_PROP_FRAME_HEIGHT);
//获取视频流的总帧数
int count_of_frames = capture2.get(CAP_PROP_FRAME_COUNT);

2. 图像的色彩空间实操

  该部分对图像主要的色彩空间进行介绍,然后通过HSV色彩空间颜色范围的过滤,实现纯色背景的替换。

  BGR色彩空间:是图像处理中最常见的颜色表示模型,一个像素点使用三通道表示(Blue, Green, Red),每一个通道占8 bit(0~255),所以对于BGR色彩空间来说,通常每一个像素点大小都是24位。

  HSV色彩空间:也是图像处理中一种常用的色彩表示模型,它将颜色分解为三个独立的部分,色调(Hue),饱和度(Saturation)和明度(Value),和BGR色彩空间一样,每一个像素点都是24 bit。

  • 色调:取值范围0-180,色调反映了颜色的基本类型,红色对应的色调大约是0,绿色大约是60,蓝色大约是120。
  • 饱和度:取值范围0-255,饱和度代表颜色鲜艳的取值范围,值越大,颜色越鲜艳,反之,颜色越接近灰度,0表示灰度。
  • 明度:表示颜色的亮度或者暗度,与饱和度不同的是,0表示黑色,取值范围是0到255。

  GRAY色彩空间,单通道灰度图像,也是图像任务处理的常用色彩空间,由于其单通道的原因,处理效率通常较高,取值范围0-255,0表示黑色,255表示白色。

  除了上述三种常见的图像处理色彩空间,还有诸如Lab、YCbCr等色彩空间,这些色彩空间都是不常见的,用于特定设备上的色彩空间。

/*这是一个图像色彩空间转换的Demo,程序中包括了如何利用HSV色彩空间的特点,通过inRange函数对色调(Hue)进行过滤,进行纯色背景的更换。
*//*inRange()函数原型:用于创建一个二值掩码图(binary mask),该掩码图的作用是,指示输入图像中,哪些像素点的值落在指定的范围之内。通常用于颜色分割或阈值(自己设定Scalar值)处理,从而突出指定范围内的颜色或阈值像素点。src:输入的源图像,可以是单通道或者是多通道图像lowerb:像素点每一个通道的下界,Array类型或者Scalar类型upperb:像素点每一天通道的上界,Array类型或者Scalar类型dst:输出的目标图像,和src大小一致,一般是二值掩码图
*/
void inRange(InputArray src, InputArray lowerb,InputArray upperb, OutputArray dst);/*色彩空间转换使用例子,利用HSV色彩空间提取ROI区域
*/
void QuickDemo::ImageColorConvert() {VideoCapture capture2("D:\\Photo\\images\\01.mp4");	if(!capture2.isOpened()){	cout << "Open failure..." << endl;return;}Mat frame2, hsv, lab, mask, result;while (true) {int c = waitKey(100 / 2);if (c == 27) {	//ESCbreak;}bool ret = capture2.read(frame2);	//读取视频帧到Mat对象if (!ret) {		//读取失败,一般视频到了尾部break;}GaussianBlur(frame2, frame2, Size(7, 7), 0);cvtColor(frame2, hsv, COLOR_BGR2HSV);cvtColor(frame2, lab, COLOR_BGR2Lab);//在hsv图像中过滤掉绿色背景,找到HSV对应绿色背景的HinRange(hsv, Scalar(25, 43, 46), Scalar(77, 255, 255), mask);	//取反获取ROI区域bitwise_not(mask, mask);	//根据二值图提取的ROI区域,对frame2进行位与操作,mask限制与操作的范围是ROI区域bitwise_and(frame2, frame2, result, mask);imshow("frame2", frame2);imshow("hsv", hsv);imshow("lab", lab);imshow("result", result);}capture2.release();
}

3. 直方图反向投影

  直方图反向投影,主要作用是估计图像中某个像素点的值预先计算好的直方图像素分布的相似度,它通常用于对象检测、颜色分割等任务。在知道直方图反向投影之前,我们首先得知道什么是直方图

  直方图,是一个统计学的概念,它可以体现出一段连续值的分布情况,比如0到99之间,有100个整数,其中,小于50的整数有50个,大于等于50的整数有50个。我们可以定义横坐标为0表示小于50的整数这一类,为1表示大于等于50的整数这一类。这样,我们就完成了直方图的定义。

  图像直方图,就是通过对图像中所有的像素点进行统计,得到像素点通道取值的分布,比如对于HSV色彩空间,可以通过图像直方图,描述出色调(Hue)的统计分布情况。

/*直方图反向投影函数原型images:Mat对象数组,也是进行直方图反向投影的目标nimages:Mat对象数组中的图像个数channels:进行反向投影的通道数组首地址,比如我们是基于HSV色彩空间的前两个通道进行反向投影,那么channels的结构为int channels[] = { 0,1 }hist:进行直方图反向投影所需的直方图对象(Mat类)backProject:直方图反向投影输出的结果ranges:图像进行直方图统计的通道范围scale:输出反向投影的缩放因子,默认1即可,值越大,可以提高输出反向投影结果的对比度,使得匹配的区域更加明显uniform:表明是否使用均值(均衡化)直方图,默认true即可
*/
void calcBackProject( const Mat* images, int nimages, const int* channels, InputArray hist, OutputArray backProject, const float** ranges, double scale = 1, bool uniform = true);/*直方图函数原型images:Mat对象数组,也是进行直方图反向投影的目标nimages:Mat对象数组中的图像个数channels:进行反向投影的通道数组首地址,比如我们是基于HSV色彩空间的前两个通道进行反向投影,那么channels的结构为int channels[] = { 0,1 }mask:通过掩码图限制直方图统计的ROI区域,默认Mat()即可hist:输出的直方图统计结果,Mat类型dims:直方图的维度,大部分情况2即可histSize:直方图的大小,int类型的数组ranges:图像进行直方图统计的通道范围uniform:表明是否使用均值(均衡化)直方图,默认true即可accumulate:表示新计算的直方图是否加在旧的直方图结果上,默认false即可
*/
void calcHist( const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform = true, bool accumulate = false);/*直方图反向投影使用例子1.确定需要进行直方图计算的匹配图像2.计算匹配图像的直方图3.基于已知图像进行直方图反向投影4.输出反向投影的结果
*/
void QuickDemo::HistogramInvertProjection() {Mat src = imread("D:\\Photo\\images\\hand.jpg", IMREAD_COLOR);Mat model = imread("D:\\Photo\\images\\hand_section.png", IMREAD_COLOR);Mat model_hsv, src_hsv;cvtColor(model, model_hsv, COLOR_BGR2HSV);cvtColor(src, src_hsv, COLOR_BGR2HSV);int h_bins = 512, s_bins = 512;		//直方图的大小int histSize[] = { h_bins,s_bins };int channels[] = { 0,1 };			//统计的通道个数Mat roiHist;						//存储直方图结果float h_range[] = { 0,180 };		//需要统计的每一个通道取值范围float s_range[] = { 0,255 };const float* ranges[] = { h_range,s_range };	//取值范围数组//获取直方图calcHist(&model_hsv, 1, channels, Mat(), roiHist, 2, histSize, ranges, true, false);//对直方图进行通道归一化,归一化的范围是[0,255]normalize(roiHist, roiHist, 0, 255, NORM_MINMAX, -1,Mat());Mat back_projection;		//存储反向投影结果calcBackProject(&src_hsv, 1, channels, roiHist, back_projection, ranges, 1.0);imshow("model_hsv", model_hsv);imshow("src_hsv", src_hsv);       imshow("roiHist", roiHist);imshow("back projection", back_projection);}

4. Harris角点检测

  角点检测是计算机视觉中获取图像特征的一种方式,那么,在图像中,什么是角点呢?角点通常由两个边缘相交而产生(注意:角点是一片像素区域,而不是一个像素点),‌对于同一场景,即使视角发生变化,角点都具有稳定性,角点无论在梯度方向上,还是梯度幅值(梯度向量的模)上都有着较大的变化。OpenCV中,提供了多种角点检测的API,这里先介绍最简单的Harris角点检测

/*Harris角点检测函数原型src:单通道图像:CV_8UC1或者CV_32FC1,通常我们传入灰度图像CV_8UC1dst:存储角点检测的图像,图像类型为CV_32FC1blockSize:角点检测的窗口大小,这个窗口的作用是计算角点响应,通过对角点检测窗口在图像上移动,通过每一个像素点的梯度矩阵,得出角点响应值ksize:Sobel导数核的大小,通常设置为3,导数核的作用是计算角点里面每一个像素点的梯度,为计算角点响应值提供参数k:Harris角点检测自由参数,通常取值为0到0.04之间,较小的值会导致更多的角点被检测出来(可能会包含噪声),较大的值会导致较少的角点被检测出来(但是结果会更加可靠)。borderType:如何处理图像边界上的像素,默认缺省参数即可
*/
void cornerHarris( InputArray src, OutputArray dst, int blockSize,int ksize, double k,int borderType = BORDER_DEFAULT);/*Mat对象归一化函数原型src:输入的图像dst:存储和src大小一致的Mat对象alpha:进行归一化的最小值边界beta:进行归一化的最大值边界norm_type:归一化的类型,如果设置了alpha和beta的值,那么常用的类型是NORM_MINMAXdtype:depth type,默认负值,规定dst类型和src类型一致,若dtype大于0,则dst只有颜色通道和src一致,图像深度depth = CV_MAT_DEPTH(dtype)mask:规定进行归一化感兴趣的区域,如果是图像的全部范围,Mat()即可
*/
void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0, nt norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());/*图像缩放,取绝对值和转换为无符号8位整数的函数原型从OpenCV官方的函数解释,该函数对图像进行了三种操作1. 缩放 + 偏置值2. 在第一步的基础上,对所有像素值取绝对值3. 在第二步的基础上,将所有的像素值转换位CV_8UC1类型src:输入需要处理的图像dst:存储处理后的图像alpha:缩放因子,如果为默认值1,不进行缩放beta:偏置值,如果为默认值0,不进行偏置很明显,如果调用该API,最后两个参数使用默认值,则只会对图像的像素值取绝对值,然后转换为CV_8UC1
*/
void convertScaleAbs(InputArray src, OutputArray dst, double alpha = 1, double beta = 0);/*使用例子,一般的,对图像进行角点检测的步骤如下:1. 获取灰度图像2. 确定角点检测的大小(计算角点响应值 corner response value),Sobel导数核的大小(计算像素点灰度值变化的梯度)3. 调用cornerHarris进行Harris角点检测4. 角点检测得出来的Mat对象是CV_32FC1类型5. 对4得到的Mat对象调用normalize(),进行归一化得到新的Mat对象6. 对5得到的Mat对象调用convertScaleAbs(),进行比例因子缩放,加上相应的偏置值,最后取绝对值得到新的Mat对象,该Mat对象就能比较直观的显示角点了7(Optional). 可以根据6得到的角点检测结果,绘制到原图中
*/
void QuickDemo::HarrisCornerDemo() {Mat src = imread("D://Photo//images//abc.png");Mat gray, binary;cvtColor(src, gray, COLOR_BGR2GRAY);Mat dst;int block_size = 5;int k_size = 3;double k = 0.04;cornerHarris(gray, dst, block_size, k_size, k);		//进行角点检测之后,得到的目标图像是单通道的FLOAT32类型Mat dst_norm = Mat::zeros(dst.size(), dst.type());normalize(dst, dst_norm, 0, 255, NORM_MINMAX, -1, Mat());convertScaleAbs(dst_norm, dst_norm);	//对图像的每一个通道进行比例因子缩放,加上偏置值,最后取绝对值得到目标图像//draw cornersfor (int row = 0; row < src.rows; ++row) {for (int col = 0; col < src.cols; ++col) {//corner response valueint rsp = dst_norm.at<uchar>(row, col);if (rsp > 100) {	//阈值设置为100//一般地,角点检测窗口的中心像素点会被看作是角点的代表像素点,Point输入的是图像坐标的值,图像遍历是按行列的,注意转换circle(src, Point(col, row), 3, Scalar(0, 0, 255), 2);	}}}imshow("src", src);imshow("gray", gray);
}

5. Shi-Tomas角点检测

  Shi-Tomas角点检测的原理和Harris角点检测一样,只是到最后计算角点响应值的方式不一样,Shi-Tomas角点检测角点响应值为矩阵 M M M的特征值较小的那个,即 R = m i n ( λ 1 , λ 2 ) R = min(\lambda_{1}, \lambda_{2}) R=min(λ1,λ2),最后,设置一个阈值,只有角点响应值 R R R大于阈值时,才认为该区域是角点

/*Shi-Tomas角点检测函数原型1,OpenCV提供了两种重载的APIimage:输入需要进行角点检测的图像,8位或者32位的单通道图像corners:输出检测到的角点(注意,这是一个点集合,但是角点是一个区域,这里的点指的是角点中的代表性点)maxCorners:检测最大的角点个数qualityLevel:角点质量的下限,这个值介于0到1之间,该参数决定了角点检测的阈值,具体的,thresh = qualityLevel * max_value,max_value为整个图像中最大的角点响应值minDistance:检测角点之间的最小欧几里得距离	mask:掩码值,Mat类型,指定角点检测的图像区域,mask对应的非零像素区域是可以进行角点检测的区域,默认Mat()即可blockSize:角点检测的窗口大小,默认3*3useHarrisDetector:是否使用Harris角点检测,默认false即可k:如果使用Harris角点检测,k为计算角点响应值所需的参数,介于0到0.04之间从Harris角点检测的原理可以知道,在进行角点响应值计算的时候,需要用到像素点的梯度值,Shi-Tomas角点检测提供的第一个API自己实现了梯度计算这一步骤
*/
void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance, InputArray mask = noArray(), int blockSize = 3, bool useHarrisDetector = false, double k = 0.04);/*Shi-Tomas角点检测函数原型2,与第一个API不同的是,该API多了一个gradientSize参数,该参数决定了计算像素点梯度时,使用Sobel导数核的大小
*/
void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance, InputArray mask, int blockSize, int gradientSize, bool useHarrisDetector = false, double k = 0.04);/*Shi_Tomas角点检测
*/
void QuickDemo::ShiTomasCornerDemo() {VideoCapture capture("D://Photo//images//bike.avi");if (!capture.isOpened()) {cout << "Open failure........" << endl;return;}int fps = capture.get(CAP_PROP_FPS);while (1) {int c = waitKey(1000 / fps);if (c == 48) {		// 0键退出break;}Mat src;bool tmp = capture.read(src);if (!tmp) {			//读取视频最后一帧,退出break;}Mat gray, dst;dst = src.clone();cvtColor(src, gray, COLOR_BGR2GRAY);vector<Point2f> corners;		//存储ShiTomas角点检测的结果double quality_level = 0.3;int blockSize = 3;int gradientSize = 3;goodFeaturesToTrack(gray, corners, 200, quality_level, 3, Mat(), blockSize, gradientSize, false);//ShiTomas角点检测接口//与cornerHarris()不同的是,Shi-Tomas角点检测API直接返回角点的点集合数组for (auto corner : corners) {circle(dst, corner, 4, Scalar(0, 0, 255), 2, LINE_AA);}imshow("src", src);imshow("dst", dst);}capture.release();
}

6. 利用Image Watch调试程序,进行视频帧分析

  本部分主要是针对Visual Studio 2019插件Image Watch的使用,通过Image Watch,在程序的调试过程中,我们可以直观的看到Mat对象的变化情况,在Image Watch中,我们可以查看图像中任意一点的像素位置和像素的取值,为了解程序逻辑提供了很大的帮助。

void QuickDemo::VideoFrameColorAnalysisAndExtract() {VideoCapture capture("D://Photo//images//balltest.mp4");if (!capture.isOpened()) {cout << "Open failure........" << endl;return;}int fps = capture.get(CAP_PROP_FPS);while (1) {Mat src, hsv, dst, mask;int c = waitKey(1000 / fps);if (c == 27) {		// ESC键退出break;}bool tmp = capture.read(src);if (!tmp) {			//读取视频最后一帧,退出cout << "提取视频帧失败" << endl;break;}GaussianBlur(src, src, Size(5, 5), 0);//去除纯色背景,提取出ROI区域,以二值图的形式表示cvtColor(src, hsv, COLOR_BGR2HSV);inRange(hsv, Scalar(26, 43, 46), Scalar(34, 255, 255), mask);mask = ~mask;Mat blueBG = Mat::zeros(src.size(), src.type());blueBG = Scalar(255, 180, 0);src.copyTo(blueBG, mask);		//以mask的规则将src图片的内容复制到blueBG中去//进行轮廓发现vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(mask, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());int index = -1;double max_area = -1;for (size_t i = 1; i < contours.size(); ++i) {			//一般的,第一个轮廓存储的是整个图像的背景if (contourArea(contours[i]) > max_area) {index = i;max_area = contourArea(contours[i]);}}//轮廓拟合,跟踪目标物体if (index >= 0) {RotatedRect rrt = minAreaRect(contours[index]);ellipse(src, rrt, Scalar(180, 160, 255), 2, LINE_AA);circle(src, rrt.center, 2, Scalar(0, 0, 255), 2, LINE_AA);}imshow("src", src);imshow("hsv", hsv);imshow("mask", mask);imshow("blueBG", blueBG);}capture.release();
}

7. 视频帧背景分析(通过背景减去器,提取前景的ROI区域)

  在OpenCV中,提供了三种视频背景分析的方法,分别是:K最近邻算法(K Nearest Neighbors,KNN),高斯混合模型(Gaussian Mixture Model,GMM),模糊积分(Fuzzy Integral)。

  • KNN算法的基本工作原理是,使用像素的历史值来估计背景模型。它通过维护一个历史帧队列,对于每个像素点,算法存储最近K帧内的值。当新的帧到来时,计算新帧像素点值与历史帧队列中K最近邻像素值的距离,如果距离大于某个阈值,则认为该像素属于前景,否则,属于背景。
  • GMM算法的基本工作原理是,对于每一个像素,算法使用一个高斯混合模型估计背景分布,模型中包含几个高斯分布,每一个高斯分布代表一个不同的背景状态,高斯分布的参数(均值和方差)是根据像素历史值动态更新的。当新帧到来时,GMM算法会计算该像素与背景模型中各个高斯分布概率密度函数的似然,如果似然低于某个阈值,则认为该像素属于前景,否则,属于背景。
  • 模糊积分是一种基于模糊逻辑的方法,它结合多个背景减除的结果来提高准确性,如使用KNN和GMM方法,生成多个背景模型。对于新帧的每一个像素,模糊积分算法会计算其在不同背景模型中的前景可能性,最后,使用模糊逻辑运算符来计算最终的前景掩码

下面通过使用混合高斯模型(GMM)的背景减除器来实现背景的消除。

/*基于混合高斯模型的背景减除器函数原型history:该参数决定,算法学习历史帧的长度,较大的值可以提供更稳定和更准确的背景模型,但也会增加处理时间varThreshold:该参数控制像素之间的方差阈值(历史帧),用于判断该像素点是否属于背景,如果一个像素方差低于阈值,则认为是背景,否则认为是前景,较高的阈值会检测更多的背景,减少误报,但可能会错过一些前景对象。不难理解,对于静态的背景,一般像素方差会较小,而对于移动的前景,像素方差会较大detectShadows:是否尝试检测阴影部分,默认为true即可,有助于提高检测的准确性return:返回值为BackgroundSubtractorMOG2类的智能指针类型,通过该智能指针,可以获取前景和背景的相关信息
*/
Ptr<BackgroundSubtractorMOG2> createBackgroundSubtractorMOG2(int history=500, double varThreshold=16, bool detectShadows=true);/*使用例子
*/
void QuickDemo::VideoBackgroundAnalysis() {VideoCapture capture("D://Photo//images//opencv_demo//vtest.avi");if (!capture.isOpened()) {cout << "Open failure........" << endl;return;}auto pMOG2 = createBackgroundSubtractorMOG2(1000, 200, true);int fps = capture.get(CAP_PROP_FPS);while (1) {Mat src, mask, bg_image;int c = waitKey(1000 / fps);if (c == 27) {		// ESC 键退出break;}bool tmp = capture.read(src);if (!tmp) {			//读取视频最后一帧,退出cout << "提取视频帧失败" << endl;break;}GaussianBlur(src, src, Size(5, 5), 0);//通过背景减去API返回的智能指针,获取前景和背景相关信息pMOG2->apply(src, mask);pMOG2->getBackgroundImage(bg_image);//形态学开操作,先腐蚀,再膨胀Mat kernel = getStructuringElement(MORPH_RECT,Size(1,5),Point(-1,-1));morphologyEx(mask, mask, MORPH_OPEN, kernel, Point(-1, -1));//基于掩码图所获得的轮廓信息,在原图中标记移动对象vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());for (size_t i = 0; i < contours.size(); ++i) {double area = contourArea(contours[i]);if (area < 200) continue;Rect rect = boundingRect(contours[i]);//绘制矩形边框rectangle(src, rect, Scalar(0, 200, 255), 2, LINE_AA);//绘制椭圆边框RotatedRect rrt = minAreaRect(contours[i]);ellipse(src, rrt, Scalar(255, 200, 0), 2, LINE_AA);  //最小外接矩形circle(src, rrt.center, 2, Scalar(0, 0, 255), 2, LINE_AA);}imshow("src", src);imshow("bg_image", bg_image);imshow("mask", mask);}capture.release();
}

8. 基于光流法的视频分析 - 上

  光流可以看成是图像结构光的变化,或者是图像亮度模式明显的移动。简单的理解就是,光流描述了连续两帧之间,像素点的位移向量。光流法是基于视频分析所提出的概念,OpenCV提供了两种光流分析方法,稀疏光流分析和稠密光流分析

/*基于稀疏光流法(KLT)的视频分析函数原型该函数原型执行的是Lucas-Kanade光流算法,是一种经典的稀疏光流计算方法,用于追踪一组预选特征点(基于角点检测获取预选特征点),在连续图像帧中的运动。preImg:8位单通道灰度图像,上一帧图像,光流计算的起点nextImg:和preImg相同大小和类型的图像,当前帧图像,光流计算的目标帧prePts:preImg中,通过角点检测出来的旧特征点nextPts:nextImg中,通过API利用角点检测计算出来的新特征点status:指示每一个特征点的追踪状态,如果特征点追踪成功,对应的status值为1,否则,status值为0,vector<uchar>类型err:指示每一个特征点的追踪误差,vector<float>winSize:用于计算光流的邻域窗口大小,更大的窗口可以提供更好的稳定性,但是可能牺牲精度,默认是Size(21, 21)maxLevel:表示光流计算中,使用金字塔结构的最深层级,金字塔用于多尺度分析(因为Lucas-Kanade算法在假设像素点移动距离很小时才有效,当移动距离很大时,考虑将图像分辨率缩小,比如底层图像是原始分辨率100*100,倒数第二层图像是50*50的分辨率),可以提高追踪的鲁棒性,默认值为3criteria:光流搜索迭代算法的终止条件,包括迭代次数和两次迭代间变化的最小阈值flags:标志位,指定额外的行为选项,默认为0即可minEigThreshshold:最小特征值阈值,用于确定光流矩阵的奇异性和稳定性,当像素点对应的光流矩阵,其最小特征值小于该阈值时,那么该像素点的光流估计会被认为是不可靠的(无效的特征点),反之,该像素点光流估计可靠。但是,并不是minEigThreshold设置的越小越好,太小的话,会导致本就不可靠的特征点被误判为可靠,默认为1e-4即可。
*/
void calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg, InputArray prevPts, InputOutputArray nextPts, OutputArray status, OutputArray err, Size winSize = Size(21,21), int maxLevel = 3, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), int flags = 0, double minEigThreshold = 1e-4);/*基于稀疏光流法(KLT)的视频分析使用例子
*/
RNG rng(1234);
void DrawLines(Mat& frame, const vector<Point2f>& pts1, const vector<Point2f>& pts2) {for (size_t i = 0; i < pts1.size(); ++i) {line(frame, pts1[i], pts2[i], Scalar(rng.uniform(0,256), rng.uniform(0, 256), rng.uniform(0, 256)), 2, 8, 0);}
}
void QuickDemo::VideoOpticalFlowAnalysis1() {VideoCapture capture("D:\\Photo\\images\\balltest.mp4");if (!capture.isOpened()) {cout << "Open video failure...." << endl;return;}namedWindow("frame", WINDOW_AUTOSIZE);//对视频第一帧进行角点检测Mat old_frame, old_gray;capture.read(old_frame);cvtColor(old_frame, old_gray, COLOR_BGR2GRAY);vector<Point2f> initPoints;vector<Point2f> feature_pts;double quality_level = 0.01;int minDistance = 10;//shi-Tomas角点检测goodFeaturesToTrack(old_gray, feature_pts, 100, quality_level, minDistance, Mat(), 3, false);Mat frame, gray;vector<Point2f> pts[2];		//定义了存储两个vector<Point2f>的数组,存储新帧和旧帧的特征点集合pts[0].insert(pts[0].end(), feature_pts.begin(), feature_pts.end());	//旧帧特征点集合initPoints.insert(initPoints.end(), feature_pts.begin(), feature_pts.end());vector<uchar> status;vector<float> error;TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 10, 0.01);	//停止标准while (true) {bool isGrabbed = capture.read(frame);if (!isGrabbed)  break;int c = waitKey(1000 / capture.get(CAP_PROP_FPS));if (c == 27)  break;cvtColor(frame, gray, COLOR_BGR2GRAY);//calculate optical flow, based on gray images, new frame's cotners will be calculated.calcOpticalFlowPyrLK(old_gray, gray, pts[0], pts[1], status, error, Size(31, 31), 3, criteria, 0);//遍历计算光流得出来的新帧点集合int i=0, k = 0;for (i = 0; i < pts[1].size(); ++i) {//距离与状态检测double dist = static_cast<double>(abs(pts[1][i].x - pts[0][i].x)) + static_cast<double>(abs(pts[1][i].y - pts[0][i].y));if (dist > 2.0 && status[i]) {//更新点集合状态,将status无效的点覆盖pts[0][k] = pts[0][i];		pts[1][k] = pts[1][i];initPoints[k] = initPoints[i];++k;int b = rng.uniform(0, 256);int g = rng.uniform(0, 256);int r = rng.uniform(0, 256);circle(frame, pts[1][i], 2, Scalar(b, g, r), 2, 8);		//在新帧中绘制特征点line(frame, pts[0][i], pts[1][i], Scalar(b, g, r), 2, 8, 0);	//新帧中绘制光流变化直线}}//update key pointspts[0].resize(k);	//只有前k个点的status有效,pts[0]随着循环的进行,会不断缩小pts[1].resize(k);initPoints.resize(k);//基于初始点绘制跟踪线DrawLines(frame, initPoints, pts[1]);//update to oldstd::swap(old_gray, gray);cv::swap(pts[0], pts[1]);//re-initif (pts[0].size() < 40) {//shi-Tomas角点检测goodFeaturesToTrack(old_gray, feature_pts, 200, quality_level, minDistance, Mat(), 3, false);pts[0].insert(pts[0].end(), feature_pts.begin(), feature_pts.end());initPoints.insert(initPoints.end(), feature_pts.begin(), feature_pts.end());}imshow("KLT_Demo", frame);}capture.release();
}

9. 基于光流法的视频分析 - 下

  稠密光流分析法,与稀疏光流分析法不同的是,稠密光流算法估计每一像素点的光流向量(运动矢量),这意味着整个图像中的所有像素都会被考虑,从而产生一个稠密的位移向量场OpenCV中,稠密光流分析法是基于$ Gunnar Farneback$算法

/*稠密光流视频分析法函数原型prev:单通道8位灰度图像,光流计算的起点next:和prev同大小、同类型的图像,光流计算的目标帧flow:存储每个像素点的光流向量,Mat<Point2f>类型pyr_scale:金字塔缩放比例,用于创建多尺度金字塔,例如,0.5表示下一层金字塔图像尺寸是上一层的一半,默认值0.5即可levels:金字塔的深度,更大的深度意味着更加精细的尺度分析,默认值3即可winsize:计算光流时使用邻域的窗口大小,窗口越大,光流场越平滑,但是可能会丢失细节,默认值15即可iterations:在每一个金字塔层中迭代优化光流的次数,更多的迭代次数可以提高光流的精度,默认值3即可poly_n:用于进行多项式扩展的邻域大小,在计算光流时,会对每个像素点的邻域进行多项式拟合,以提高估计的准确性,默认值5即可poly_sigma:多项式拟合时的高斯权重函数的标准差,它决定了邻域内哪些点对中心点的影响更大默认值1.1即可flags:用于指定附加行为的标志位,默认值0即可*/
void calcOpticalFlowFarneback(InputArray prev, InputArray next, InputOutputArray flow, double pyr_scale, int levels, int winsize, int iterations, int poly_n, double poly_sigma, int flags);/*将笛卡尔系的二维坐标向量转换为极坐标系x:输入的x坐标,通常是单通道的Mat类对象,CV_32F或CV_64Fy:输入的y坐标,类型和x的类型一致magnitude:存储极坐标系下向量的幅度(向量的模)angle:存储极坐标系下向量的角度,即与x轴正向的夹角angleDegrees:角度的单位,默认false即弧度制,true即角度制
*/
void cartToPolar(InputArray x, InputArray y, OutputArray magnitude, OutputArray angle, bool angleInDegrees = false);/*稠密光流分析法使用例子,基本步骤1. 对视频流第一帧图像进行灰度化,作为第一个旧帧2. 在循环中,提取第一个新帧,灰度化3. 调用稠密光流分析法函数4. 提取光流向量在x轴和y轴方向上的偏移量5. 将偏移量转换为极坐标表示6. 将转换为极坐标的角度映射到0~180,极坐标向量的幅度映射到0~255(利用归一化),然后将角度的映射作为H,设置mv[1]为S,幅度的映射作为V,进行通道合并成HSV图像7. 最后,将HSV图像转换为BGR图像,将稠密光流分析的结果展示出来*/
void QuickDemo::VideoOpticalFlowAnalysis2() {VideoCapture capture("D://Photo//images//opencv_demo//vtest.avi");if (!capture.isOpened()) {cout << "Open failure........" << endl;return;}Mat frame, pre_frame;Mat gray, pre_gray;capture.read(pre_frame);cvtColor(pre_frame, pre_gray, COLOR_BGR2GRAY);Mat hsv = Mat::zeros(pre_frame.size(), pre_frame.type());Mat mag = Mat::zeros(pre_frame.size(), CV_32FC1);		//32位单通道浮点数Mat ang = Mat::zeros(pre_frame.size(), CV_32FC1);		Mat xpts = Mat::zeros(pre_frame.size(), CV_32FC1);		Mat ypts = Mat::zeros(pre_frame.size(), CV_32FC1);		vector<Mat> mv;split(hsv, mv);		//通道分离Mat result;double fps = capture.get(CAP_PROP_FPS);while (1) {int c = waitKey(1000.0 / fps);if (c == 27) break;bool tmp = capture.read(frame);if (!tmp) {			//读取视频最后一帧,退出break;}	cvtColor(frame, gray, COLOR_BGR2GRAY);Mat flow;		//稠密光流分析法输出检测的像素点,也就是每个像素点的光流向量calcOpticalFlowFarneback(pre_gray, gray, flow, 0.5, 3, 15, 3, 5, 1.2, 0);//提取光流向量的x和y轴偏移量for (int row = 0; row < flow.rows; ++row) {for (int col = 0; col < flow.cols; ++col) {const Point2f& flow_xy = flow.at<Point2f>(row, col);xpts.at<float>(row, col) = flow_xy.x;ypts.at<float>(row, col) = flow_xy.y;}}//将光流向量转换为极坐标表示cartToPolar(xpts, ypts, mag, ang);//向量的角度映射到0到180度表示ang = ang * 180 / CV_PI / 2;//将向量的模归一化,映射到0到255normalize(mag, mag, 0, 255, NORM_MINMAX);//对极坐标系下向量的幅度和角度取绝对值,转换为CV_8UC1类型convertScaleAbs(mag, mag);		convertScaleAbs(ang, ang);//将类型转换后的ang和mag进行通道合并,hsv色彩空间mv[0] = ang;mv[1] = Scalar(255);mv[2] = mag;merge(mv, hsv);cvtColor(hsv, result, COLOR_HSV2BGR); imshow("frame", frame);imshow("result", result);}capture.release();
}

10. 均值迁移分析法

/*均值迁移分析法函数原型
*//*均值迁移分析法使用例子
*/
void QuickDemo::MeanValueShiftAnalysis() {VideoCapture capture("D://Photo//images//balltest.mp4");if (!capture.isOpened()) {cout << "Open failure........" << endl;return;}double fps = capture.get(CAP_PROP_FPS);Mat frame, hsv, hue, mask, hist, backproj;capture.read(frame);bool init = true;Rect trackWindow;int hsize = 16;float hranges[] = { 0,180 };const float* ranges[] = { hranges };//从frame中提取ROI区域Rect selection = selectROI("MeanShift Demo", frame, true, false);while (1) {int c = waitKey(1000.0 / fps);if (c == 27) break;bool isCrabbed = capture.read(frame);if (!isCrabbed)	break;cvtColor(frame, hsv, COLOR_BGR2HSV);inRange(hsv, Scalar(26, 43, 46), Scalar(34, 255, 255), mask);int ch[] = { 0,0 };hue.create(hsv.size(), hsv.depth());mixChannels(&hsv, 1, &hue, 1, ch, 1);	//通道混合//初始化if (init) {		Mat roi(hue, selection);Mat mask_roi(mask, selection);//计算直方图calcHist(&roi, 1, 0, mask_roi, hist, 1, &hsize, ranges);normalize(hist, hist, 0, 255, NORM_MINMAX);trackWindow = selection;init = false;}//计算直方图反向投影calcBackProject(&hue, 1, 0, hist, backproj, ranges);//直方图反向投影的结果与掩码图进行与操作backproj &= mask;//迭代次数和两次迭代间变化的最小阈值TermCriteria criteria = TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 30, 1);meanShift(backproj, trackWindow, criteria);//均值迁移分析RotatedRect rrt = CamShift(backproj, trackWindow, criteria);//绘制矩形rectangle(frame, trackWindow, Scalar(0, 0, 255), 2, LINE_AA);//绘制椭圆ellipse(frame, rrt, Scalar(255, 200, 0), 2, LINE_AA);imshow("MeanShift Demo", frame);}capture.release();
}

总结

  至此,OpenCV图像与视频分析相关的笔记整理完毕,该系列笔记包含了OpenCV大部分传统的方法,来进行图像处理,非常适合OpenCV的入门。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Mendix 创客访谈录|Mendix赋能汽车零部件行业:重塑架构,加速实践与数字化转型
  • 数字电子技术-进制
  • WPS快捷键
  • HarmonyOS开发实战( Beta5版)状态管理优秀实践
  • Spark MLlib模型训练—回归算法 GLR( Generalized Linear Regression)
  • IntelliJ IDEA修改默认.m2和.gradle缓存路径
  • JAVA—网络通信
  • 项目文章|PNAS:中国农大田见晖教授团队揭示DNA甲基化保护早期胚胎线粒体基因组稳定性
  • 使用FFmpeg的AVFilter转换YUV到RGB
  • 深度学习示例2-多输入多输出的神经网络模型
  • 20.弹窗组件封装
  • Linux【4】拷贝移动 文件内容
  • 云计算的成本:您需要了解的 AWS 定价信息
  • stm32 8080时序驱动lcd屏幕
  • PDM系统详细介绍
  • Apache的80端口被占用以及访问时报错403
  • Druid 在有赞的实践
  • emacs初体验
  • mongodb--安装和初步使用教程
  • php ci框架整合银盛支付
  • Python打包系统简单入门
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • Sequelize 中文文档 v4 - Getting started - 入门
  • underscore源码剖析之整体架构
  • vue总结
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 记录:CentOS7.2配置LNMP环境记录
  • 如何学习JavaEE,项目又该如何做?
  • 入手阿里云新服务器的部署NODE
  • 为什么要用IPython/Jupyter?
  • 译自由幺半群
  • 原生 js 实现移动端 Touch 滑动反弹
  • - 转 Ext2.0 form使用实例
  • Prometheus VS InfluxDB
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • $NOIp2018$劝退记
  • (0)Nginx 功能特性
  • (9)STL算法之逆转旋转
  • (AtCoder Beginner Contest 340) -- F - S = 1 -- 题解
  • (javaweb)Http协议
  • (笔记)M1使用hombrew安装qemu
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (四十一)大数据实战——spark的yarn模式生产环境部署
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (小白学Java)Java简介和基本配置
  • (转)一些感悟
  • .Net IOC框架入门之一 Unity
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .net2005怎么读string形的xml,不是xml文件。
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .NET的微型Web框架 Nancy
  • .net与java建立WebService再互相调用