我正在尝试制作一个应用程序,该应用程序将通过网络摄像头识别一张脸(只需返回脸的地标即可)。我已经为网络摄像头编写了代码,并通过位图对人脸进行了分析。但是,当我执行以下代码时,网络摄像头会冻结。如何使用异步/等待来解决此问题?另一个问题是,如何使我仅每1秒调用一次AnalyzeFace
方法?我还真的不知道该怎么做,所以我需要您的建议。
FaceDetectionFromFrame
检测到人脸并在其周围绘制一个矩形
form.scanPictureBox.Image
在图片框中显示当前帧
AnalyzeFace
返回脸部的分析属性
我的帧处理代码:
private static void ProcessFrame(object sender, EventArgs e)
{
List<string> faceList = new List<string>();
using (var imageFrame = capture.QueryFrame().ToImage<Bgr, Byte>())
{
FaceDetection.FaceDetectionFromFrame(imageFrame); // Face detection
var form = FormFaceDetection.Current;
form.scanPictureBox.Image = imageFrame.Bitmap;
faceList.Add(FaceRecognition.AnalyzeFace(imageFrame.Bitmap));
}
}
答案 0 :(得分:2)
尽管您没有这么说,但在我看来,`ProcessFrame是一个函数,只要相机希望通知您有新的Frame可供处理,就会调用该函数。
显然,处理此新帧需要花费大量时间。甚至有可能是尚未处理前一个图像时就已经获取了一个新图像。
async-await并不能帮助您:您必须尽可能快地抓取图像,并命令另一个线程来处理获取的帧。您应该尽快从事件处理程序中返回,最好是在处理完帧之前。另一个可能的要求是,应按抓取的顺序处理和显示抓取的帧。
下面我将告诉您有关异步等待的更多信息。首先,我将为您的相机问题提出解决方案。
您的问题的解决方案在producer-consumer design pattern中。生产者产生必须由消费者处理的数据。可以比消费者处理数据更快或更慢地产生数据。如果在消费者处理之前生产的数据之前有新数据可用,则生产者应将生产的数据保存在某个地方并继续生产。
只要消费者处理了生产数据,它就会检查是否还有更多生产数据并开始处理。
这种情况一直持续到生产者通知消费者不再生产任何数据为止。
在MSDN Task Parallel Library (TPL)中使用所有多线程安全性实现了这种生产消费者模式。可作为nuget package: Microsoft Tpl Dataflow
下载您需要两个线程:生产者和消费者。制片人尽可能快地生成图像。产生的图像保存在BufferBlock<Frame>
中。
不同的线程将消耗生成的图像帧。
// the buffer to save frames that need to be processed:
private readonly BufferBlock<ImageFrame> buffer = new BufferBlock<ImageFrame>();
// event handler to be called whenever the camera has an image
// similar like your ProcessFrame
public async void OnImageAvailableAsync(object sender, EventArgs e)
{
// the sender is your camera who reports that an image can be grabbed:
ImageGrabber imageGrabber = (ImageGrabber)sender;
// grab the image:
ImageFrame grabbedFrame = imageGrabber.QueryFrame();
// save it on the buffer for processing:
await this.buffer.SendAsync(grabbedFrame);
// finished producing the image frame
}
消费者:
// this task will process grabbed images that are in the buffer
// until there are no more images to process
public async Task ProcessGrabbedImagesAsync()
{
// wait for data in the buffer
// stop waiting if no data is expected anymore
while (await buffer.OutpubAvailableAsync())
{
// The producer put some data in the buffer.
// Fetch it and process it.
// This may take some time, which is no problem. If the producer has new frames
// they will be saved in the buffer
FaceDetection.FaceDetectionFromFrame(imageFrame); // Face detection
var form = FormFaceDetection.Current;
form.scanPictureBox.Image = imageFrame.Bitmap;
faceList.Add(FaceRecognition.AnalyzeFace(imageFrame.Bitmap));
}
}
用法:
// Start a consumer task:
Task taskConsumer = task.Run( () => ProcessGrabbedImagesAsync());
// subscribe to the camera's event:
camera.EventImageAvailable += OnImageAvailableAsync;
camera.StartImageGrabbing();
// free to do other things
您将需要语句来停止图像捕获
camera.StopImageGrabbing();
// unsubscribe:
camera.EventImageAvailable -= OnImageAvailableAsync;
// notify that no images will be produced:
buffer.Complete();
// await until the consumer is finished processing all produced images:
await taskConsumer;
async-await仅在您的进程必须空闲地等待其他进程完成时才有意义,例如,在等待数据库查询完成,正在写入文件或从中获取某些信息时互联网。在这段时间内,您的进程通常只会闲着等待,直到另一个进程完成。
要使函数异步:
Task
代替void,返回Task<TResult>
代替TResult
Task
await
返回的Task<TResult>
就在您需要异步函数的结果之前。await Task<TResult>
的返回值为TResult
; await Task
的回报无效。乍一看,似乎使您的流程可以解决您的问题。
private async void ProcessFrameAsync(object sender, EventArgs e)
{ // async event handlers return void instead of Task
var grabbedImage = await camera.FetchImageAsync();
// or if your camera has no async function:
await Task.Run( () => camera.FetchImage());
// this might be a length process:
ProcessImaged(grabbedImage);
ShowImage(grabbedImage);
}
如果在完全处理前一个图像之前有新图像可用,则再次调用事件处理程序。如果第二张图像的处理比第一张图像的处理要快,那么它会在显示第一张图像之前显示出来。
此外,您还必须注意这两个过程不会互相干扰。
因此,就您而言,使事件处理程序异步不是一个好主意。
仅当确定在下一个事件引发之前事件已完成时才使事件处理程序异步
如果不是通过事件抓取图像,而是直接通过向相机询问新图像来抓取图像,则async-await会有所帮助:
async Task GrabAndProcessImages(CancellationToken token)
{
// grab the first image:
var grabbedImage = await camera.GrabImageAsync(token);
while (!token.CancellationRequested)
{
// start grabbing the next image, do not wait for it yet
var taskGrabImage = camera.GrabImageAsync(token);
// because I'm not awaiting, I'm free to do other things
// like processing the last grabbed image:
ProcessImage(grabbedImage);
// await for the next image:
grabbedImage = await taskGrabImage;
}
}
用法:
using(var cancellationTokenSource = new cancellationTokenSource())
{
Task taskProcessImages = grabAndProcessImages(cancellationTokenSource.Token);
// because I did not await, I'm free to do other things,
DoSomeThingElse();
// To stop grabbing images: cancel the cancellationTokenSource:
cancellationTokenSource.Cancel();
// or if you want to be sure that it grabbed for at least 30 seconds:
cancellationTokeSource.CanceAfter(TimeSpan.FromSeconds(30));
// still free to do something else,
DoSomeThingElse();
// before returning: await until the image grabbing task completes:
await taskProcessImages;
// if here, you are certain that processing images is completed
}