在不触发网络摄像头的情况下调用人脸识别 API

Call face recognition API without triggering webcam

我正在尝试制作一个应用程序,它可以从网络摄像头识别人脸(只是 return 人脸的标志)。我已经为网络摄像头编写了代码,并从位图中进行面部分析。但是当我执行下面的代码时,网络摄像头就冻结了。我如何使用 async/await 来解决这个问题?另一个问题是,我怎样才能让我每 1 秒调用一次 AnalyzeFace 方法?我真的不知道该怎么做,所以我需要你的建议。

FaceDetectionFromFrame 检测到人脸并在其周围绘制一个矩形

form.scanPictureBox.Image 在图片框中显示当前帧

AnalyzeFace returns 分析了面部的属性

我的帧处理代码:

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));
        }
    }

虽然你没有这么说,但在我看来,`ProcessFrame 是一个函数,每当相机想要通知你有新的 Frame 可供处理时调用。

显然处理这个新帧需要相当长的时间。甚至可能是在前一张图像尚未处理时已经抓取了一张新图像。

async-await 不会帮助你:你必须尽快抓取图像并命令另一个线程来处理获取的帧。您应该尽快从事件处理程序中 return ,最好是在处理帧之前。另一个可能的要求是抓取的帧应该按照抓取的顺序进行处理和显示。

下面我会告诉你更多关于async-await的知识。首先我会为你的相机问题提出一个解决方案。

您的问题的解决方案在 producer-consumer design pattern 中。生产者生产必须由消费者处理的数据。数据的生成速度可能比消费者处理数据的速度更快或更慢。如果在先前生产的数据被消费者处理之前有新数据可用,生产者应该将生产的数据保存在某个地方并继续生产。

每当消费者处理完生产数据时,它会检查是否有更多生产数据并开始处理它。

这一直持续到生产者通知消费者不再生产数据为止。

此生产者-消费者模式在 MSDN Task Parallel Library (TPL). This is downloadable as a 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 仅在您的进程必须空闲地等待其他进程完成时才有意义,例如,当它正在等待数据库查询完成,或要写入的文件,或等待某些信息时从互联网上获取。在此期间,您的进程通常会空闲地等待,直到另一个进程完成。

要使函数异步:

  • 将其声明为异步
  • return Task 而不是 void 和 Task<TResult> 而不是 TResult
  • 唯一例外:事件处理程序 return 无效而不是任务:没有人等待事件处理程序完成
  • 在你的异步函数中调用其他异步函数。
  • 如果您还不需要异步函数的结果,但可以在异步函数完成之前做其他事情。不要等待它,记住 returned Task
  • await returned Task<TResult> 就在您需要异步函数的结果之前。
  • await Task<TResult>的return为TResultawait Task 有空 return.
  • 好的做法:同时创建一个异步函数和一个非异步函数

乍一看,制作您的过程似乎可以解决您的问题。

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);
}

如果在上一个图像处理完成之前有新图像可用,则再次调用事件处理程序。如果第二张图片的处理速度比第一张图片的处理速度快,那么它会在第一张图片显示之前显示。

此外,您还必须注意这两个过程不会相互干扰。

因此,在您的情况下,使事件处理程序异步并不是一个好主意。

Only make event handlers async if you are certain that the event is completed before the next event is raised

如果图像抓取不是通过事件完成,而是直接向相机请求新图像,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
}