在本文中,将探讨如何在Visual Studio2017中创建一个C#项目,该项目利用两个库来处理视频文件中的帧。将展示如何循环遍历这些帧,并进行一些基本的图像处理操作。请确保已经安装了Visual Studio 2017,并准备好了相应的库。
在进行任何处理之前,需要从视频文件中获取一帧。这可以通过使用VideoCapture
类来轻松完成(许多教程提到了Capture
类,但在最近的Emgu版本中已经不可用)。
请查看示例项目的Main
方法:
private const string BackgroundFrameWindowName = "Background Frame";
private static Mat backgroundFrame = new Mat();
static void Main(string[] args)
{
string videoFile = @"PUT A PATH TO VIDEO FILE HERE!";
using (var capture = new VideoCapture(videoFile))
{
if (capture.IsOpened)
{
backgroundFrame = capture.QueryFrame();
CvInvoke.Imshow(BackgroundFrameWindowName, backgroundFrame);
VideoProcessingLoop(capture, backgroundFrame);
}
else
{
Console.WriteLine($"Unable to open {videoFile}");
}
}
}
VideoCapture
类有四种构造函数版本。使用的重载版本接受一个string
参数,即视频文件或视频流的路径。其他版本允许连接到摄像头。如果正确设计程序,从文件输入切换到网络摄像头可能就像改变new VideoCapture
调用一样简单!
一旦创建了VideoCapture
实例,可以通过访问IsOpened
属性来确认是否成功打开(可能是路径错误或缺少编解码器)。
VideoCapture
提供了几种获取帧的方法,但发现最方便的是调用QueryFrame
方法。此方法返回Mat
类实例(已经从第一部分知道了),并移动到下一帧。如果找不到下一帧,则返回null
。可以利用这个事实轻松地遍历视频。
无人机检测项目基于找到背景帧和其他帧之间的差异。假设可以从视频中获取的第一帧作为背景,因此在创建VideoCapture
对象后立即调用QueryFrame
:
backgroundFrame = capture.QueryFrame();
加载背景后,可以通过调用Imshow
方法来检查它的外观(也知道它来自第一部分):
CvInvoke.Imshow(BackgroundFrameWindowName, backgroundFrame);
在视频中总是像减去帧一样容易找到(有意义的!)差异吗?不,不是的。首先,背景可能不是静态的(想象一下无人机在被风吹动的树前飞行,或者如果房间的光线发生了显著变化)。第二个挑战可能来自相机的移动。拥有固定的背景和相机位置使无人机检测任务对于初学者的OpenCV教程来说足够简单,而且并不完全不现实。视频检测/识别通常用于完全受控的环境,如工厂的一部分... OpenCV能够处理更复杂的场景 - 可以阅读有关背景减法技术和光流的更多信息...
知道可以使用QueryFrame
获取单个帧图像(Mat
实例)并移动到下一帧,知道QueryFrame
如果无法继续则返回null
。让利用这些知识构建一个遍历帧循环的方法:
private static void VideoProcessingLoop(VideoCapture capture, Mat backgroundFrame)
{
var stopwatch = new Stopwatch();
int frameNumber = 1;
while (true)
{
Mat rawFrame = capture.QueryFrame();
if (rawFrame != null)
{
frameNumber++;
stopwatch.Restart();
ProcessFrame(backgroundFrame, Threshold, ErodeIterations, DilateIterations);
stopwatch.Stop();
WriteFrameInfo(stopwatch.ElapsedMilliseconds, frameNumber);
ShowWindowsWithImageProcessingStages();
int key = CvInvoke.WaitKey(0);
if (key == 27)
Environment.Exit(0);
}
else
{
capture.SetCaptureProperty(CapProp.PosFrames, 0);
frameNumber = 0;
}
}
}
在每次循环迭代中,从视频文件中抓取一个帧。然后将其传递给ProcessFrame
方法,该方法执行图像差异、噪声去除、轮廓检测和绘制(将在下一篇文章中详细讨论)...调用ProcessFrame
被System.Diagnostics.Stopwatch
的使用所包围 - 这样,可以测量视频处理性能。笔记本电脑处理每个帧只需要大约1.5ms - 告诉过OpenCV很快!
如果QueryFrame
返回null
,则程序通过调用VideoCapture
实例上的SetCaptureProperty
方法返回到第一帧(视频将再次被处理)。
WriteFrameInfo
在帧的左上角放置文本,包含有关其编号和处理时间的信息。ShowWindowsWithImageProcessingStages
确保可以看到当前(原始)帧、背景帧、中间帧和最终帧在单独的窗口中...这两种方法将在下一篇文章中展示。
while
循环将无限期地旋转,除非通过在显示帧的任何窗口中按下Escape
键来停止程序执行(不是控制台窗口!)。如果将0
作为WaitKey
参数传递,则程序等待直到按下某个键。这让可以随时查看每个帧。如果向WaitKey
传递其他数字,则程序将等待直到按下键或延迟到期。可能会使用它以指定的帧速率自动播放视频:
int fps = (int)capture.GetCaptureProperty(CapProp.Fps);
int key = CvInvoke.WaitKey(1000 / fps);