https://www.hahack.com/wiki/opencv-video.html#
参考资料:
- 《OpenCV 2 Computer Vision Application Programming Cookbook》
- 《The OpenCV Reference Manual》
读取视频
使用 CV::VideoCapture
来读取视频序列。
#include <stdio.h>
#include <string>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
int main()
{
// Open the video file
cv::VideoCapture capture("../stomp.avi");
// check if video successfully opened
if (!capture.isOpened())
return 1;
// Get the frame rate
double rate = capture.get(CV_CAP_PROP_FPS);
bool stop(false);
cv::Mat frame; // current video frame
cv::namedWindow("Extracted Frame");
// Delay between each frame in ms
// corresponds to video frame rate
int delay = 1000 / rate;
// for all frames in video
while (!stop){
// read next frame if any
if (!capture.read(frame))
break;
cv::imshow("Extracted Frame", frame);
// introduce a delay
// or press key to stop
if (cv::waitKey(delay) >= 0)
stop = true;
}
// Close the video file
// Not required since called by destructor
capture.release();
}
也可以通过类似的方法读入摄像头捕捉的视频,要改动的地方仅仅是将上面的视频文件名改为摄像头的 ID,默认的摄像头 ID 为 0。
写入视频
使用 CV::VideoWriter
来写入视频。
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
#include <dbg.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
int main()
{
VideoCapture cap;
cap.open(0);
namedWindow("MyVideo", 1);
double dWidth = cap.get(CV_CAP_PROP_FRAME_WIDTH);
double dHeight = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
Size frameSize(static_cast<int>(dWidth), static_cast<int>(dHeight));
VideoWriter oVideoWriter("MyVideo.avi", CV_FOURCC('P','I','M','1'), 20, frameSize, true); // initialize the VideoWriter objetct
while (1) {
Mat frame;
bool bSuccess = cap.read(frame);
if (!bSuccess){ // if not success, break loop
cout << "ERROR: cannot read a frame from video file" << endl;
break;
}
oVideoWriter.write(frame);
imshow("MyVideo", frame); // show the frame in "MyVideo" window
if(waitKey(10) == 27) {// wait for ESC key
cout << "ESC key is pressed by user" << endl;
break;
}
}
return 0;
}
目标检测
mean-shift 和 camshift
均值漂移(mean-shift)
mean-shift 算法是一种在一组数据的密度分布中寻找局部极值的稳定 [1] 的方法。若分布是连续的,处理过程就比较容易,这种情况下本质上只需要对数据的密度直方图应用爬山算法即可。然而,对于离散的数据集,这个问题在某种程度上是比较麻烦的。
mean-shift 算法的步骤如下:
- 选择搜索窗口。
- 计算窗口(可能是带权重的)的重心。
- 将窗口的中心设置在计算出的重心处。
- 返回第 2 步,直到窗口的位置不再变化(通常会)。
OpenCV 提供 cv::meanshift()
函数来进行 mean-shift 算法跟踪。
int cv::meanShift(InputArray probImage, Rect& window, TermCriteria criteria)
其中,
probImage
- 图像直方图反投影后的结果;window
- 初始的查找窗口,即要跟踪的区域;criteria
- 迭代搜索算法的终止条件,主要由 mean-shift 移动的最大迭代次数和可视为窗口位置收敛的最小移动距离组成。- 返回的是收敛时算法的迭代次数。
对于第一个参数 probImage
,可以直接使用 cv::calcBackProject()
得到的结果。但《OpenCV 2 Computer Vision Application Programming Cookbook》建议先把图像转换到 HSV 颜色空间,然后使用 Hue 单通道的直方图的反投影变换结果作为 probImage
;
获取彩色图像的 Hue 通道的直方图算法实现如下:
// Computes the 1D Hue histogram with a mask.
// BGR source image is converted to HSV
cv::MatND getHueHistogram(const cv::Mat &image,
int minSaturation = 0) {
cv::MatND hist;
// Convert to HSV color space
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
// Mask to be used (or not)
cv::Mat mask;
if (minSaturation > 0) {
// Spliting the 3 channels into 3 images
std::vector<cv::Mat> v;
cv::split(hsv, v);
// Mask out the low saturated pixels
cv::threshold(v[1], mask, minSaturation,
255, cv::THRESH_BINARY);
}
// Prepare arguments for a 1D hue histogram
hranges[0]= 0.0;
hranges[1]= 180.0;
channels[0]= 0; // the hue channel
// Compute histogram
cv::calcHist(&hsv,
1, // histogram of 1 image only
channels, // the channel used
mask, // no mask is used
hist, // the resulting histogram
1, // it is a 1D histogram
histSize, // number of bins
ranges // pixel value range
);
return hist;
}
使用 cv::meanshift()
函数在两幅图像间跟踪某一物体的步骤如下:
- 读入第一张图像,定义好目标跟踪窗口,即感兴趣区域 ROI 。
- 计算这个 ROI 的 Hue 通道的直方图; <