.NET 摄像头采集

目录

前言

AForge.NET

WPFMediaKit

MediaCapture(UWP)

其它


前言

本文主要介绍摄像头(相机)如何采集数据,用于类似摄像头本地显示软件,以及流媒体数据传输场景如传屏、视讯会议等。

摄像头采集有多种方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),网上一些文章以及github已经有很多介绍,这里总结、确认技术选型给大家一个参考

AForge.NET

AForge视频库是基于DirectShow技术开发的,提供了捕捉、处理和显示视频流接口,以及图像丰富的图像处理功能,如滤镜、特征提取和物体检测。详见官网开源仓库 andrewkirillov/AForge.NET(github.com)

我们下面看下AForge录制代码,安装Nuget包依赖:

<PackageReference Include="AForge.Video" Version="2.2.5" />
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />

摄像头显示:

private void StartButton_OnClick(object sender, RoutedEventArgs e)
{
    // 获取所有视频输入设备
    var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
    if (videoDevices.Count > 0)
    {
        // 选择第一个视频输入设备
        var videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
        // 注册NewFrame事件处理程序
        videoSource.NewFrame += new NewFrameEventHandler(videoSource_NewFrame);
        // 开始摄像头视频源
        videoSource.Start();
    }
}

private async void videoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    // 获取当前的视频帧,显示
    var image = ToBitmapImage(eventArgs.Frame);
    await Dispatcher.InvokeAsync(() => { CaptureImage.Source = image; });
}

摄像头录制视频流:

private async void videoSource_NewFrame1(object sender, NewFrameEventArgs eventArgs)
{
    // 获取当前的视频帧
    // 将Bitmap转换为byte[],用于流媒体传输
    byte[] byteArray = BitmapToByteArray(eventArgs.Frame, out int stride);
    // 将byte[]转换为BitmapImage,用于临时展示
    BitmapImage image = ByteArrayToBitmapImage(byteArray, eventArgs.Frame.Width, eventArgs.Frame.Height, stride, eventArgs.Frame.PixelFormat);
    await Dispatcher.InvokeAsync(() => { CaptureImage.Source = image; });
}

其中的数据转换,这里要把分辨率stride同byte[]数据一同储存,不然后续数据是无法处理的:

// 将Bitmap转换为byte[]
public byte[] BitmapToByteArray(Bitmap bitmap, out int stride)
{
    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat);
    //stride是分辨率水平值,如3840
    stride = bitmapData.Stride;
    int bytes = Math.Abs(bitmapData.Stride) * bitmap.Height;
    byte[] rgbValues = new byte[bytes];

    // 复制位图数据到字节数组
    Marshal.Copy(bitmapData.Scan0, rgbValues, 0, bytes);

    bitmap.UnlockBits(bitmapData);
    return rgbValues;
}

// 将byte[]转换为BitmapImage
public BitmapImage ByteArrayToBitmapImage(byte[] byteArray, int width, int height, int stride, PixelFormat pixelFormat)
{
    var bitmapImage = new BitmapImage();
    using (var memoryStream = new MemoryStream())
    {
        var bmp = new Bitmap(width, height, stride, pixelFormat, Marshal.UnsafeAddrOfPinnedArrayElement(byteArray, 0));
        // 保存到MemoryStream中
        bmp.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
        memoryStream.Seek(0, SeekOrigin.Begin);
        bitmapImage.BeginInit();
        bitmapImage.StreamSource = memoryStream;
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.EndInit();
        bitmapImage.Freeze();
    }
    return bitmapImage;
}

详见Demo代码 kybs00/AForgeNETDemo (github.com)

经验证,延迟较大,对比Windows系统相机不够清晰

WPFMediaKit

WPFMediaKit也是基于DirectShow的,它提供了一些封装便于在WPF应用中使用媒体功能。Sascha-L/WPF-MediaKit (github.com)

使用 WPFMediaKit 要录制摄像头视频,需要结合 WPFMediaKit 提供的视频捕获功能和其他库(例如 AForge 或 FFmpeg)来实现录制功能。

这里引用WPFMediaKit、AForge.Video.FFMPEG俩个Nuget包,然后通过定时器捕获当前视频帧:

var bitmap = new Bitmap(videoCaptureElement.Width, videoCaptureElement.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),System.Drawing.Imaging.ImageLockMode.WriteOnly,bitmap.PixelFormat);
try
{
    videoCaptureElement.VideoCaptureDevice.GetCurrentVideoFrame(out IntPtr frame);
    System.Runtime.InteropServices.Marshal.Copy(frame, 0, bitmapData.Scan0, videoCaptureElement.Width * videoCaptureElement.Height * 3);
}
finally
{
    bitmap.UnlockBits(bitmapData);
}

这个定时器的实现比较low。延时较低、较流畅,但与Win系统相机对比也是不够清晰

MediaCapture(UWP)

MediaCapture是Windows 8及以上版本的WinRT API,专为捕获音频、视频和照片设计。

MediaCaptuer是UWP应用的API 使用 MediaCapture 捕获基本的照片、视频和音频 - UWP applications | Microsoft Learn,要在WPF内使用需要引入俩个Nuget包:

<PackageReference Include="Microsoft.Toolkit.Wpf.UI.XamlHost" Version="6.1.2" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.26100.1" />

初始化MediaCapture:

var mediaCapture =new MediaCapture();
var videos = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
var settings = new MediaCaptureInitializationSettings()
{
    VideoDeviceId = videos[0].Id,
    StreamingCaptureMode = StreamingCaptureMode.Video,
};
await mediaCapture.InitializeAsync(settings);

分几个场景,分别输出Demo。录制本地文件,注释已经很详细了并不重复解释,直接看代码:

private MediaCapture _mediaCapture;
private InMemoryRandomAccessStream _randomAccessStream;
private async void StartButton_OnClick(object sender, RoutedEventArgs e)
{
    // 1. 初始化 MediaCapture 对象
    var mediaCapture = _mediaCapture = new MediaCapture();
    var videos = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
    var settings = new MediaCaptureInitializationSettings()
    {
        VideoDeviceId = videos[0].Id,
        StreamingCaptureMode = StreamingCaptureMode.Video,
    };
    await mediaCapture.InitializeAsync(settings);

    // 2. 设置要录制的数据流
    var randomAccessStream = _randomAccessStream = new InMemoryRandomAccessStream();
    // 3. 配置录制的视频设置
    var mediaEncodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);
    // 4. 开始录制
    await mediaCapture.StartRecordToStreamAsync(mediaEncodingProfile, randomAccessStream);
}

private async void StopButton_OnClick(object sender, RoutedEventArgs e)
{
    // 停止录制
    await _mediaCapture.StopRecordAsync();
    // 处理录制后的数据,保存至"C:\Users\XXX\Videos\RecordedVideo.mp4"
    var storageFolder = Windows.Storage.KnownFolders.VideosLibrary;
    var file = await storageFolder.CreateFileAsync("RecordedVideo.mp4", Windows.Storage.CreationCollisionOption.GenerateUniqueName);
    using var fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
    await RandomAccessStream.CopyAndCloseAsync(_randomAccessStream.GetInputStreamAt(0), fileStream.GetOutputStreamAt(0));
    _randomAccessStream.Dispose();
}

摄像头显示,通过UWP-WindowsXamlHost承载画面(置顶):

private async void StartButton_OnClick(object sender, RoutedEventArgs e)
{
    _mediaCapture = new MediaCapture();
    var videos = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
    var settings = new MediaCaptureInitializationSettings()
    {
        VideoDeviceId = videos[0].Id,
        StreamingCaptureMode = StreamingCaptureMode.Video,
    };
    await _mediaCapture.InitializeAsync(settings);
    //显示WindowsXamlHost
    VideoViewHost.Visibility = Visibility.Visible;
    //绑定画面源
    _captureElement.Source = _mediaCapture;
    await _mediaCapture.StartPreviewAsync();
}

MediaCapture是UWP平台的实现方案,直接给CaptureElement赋值绑定画面源。直接用CaptureElement渲染速度很快,这个实现逻辑同windows系统相机是一样的

另外,使用MediaCapture也可以捕获画面帧事件,用于流媒体数据捕获收集:

// 配置视频帧读取器
var frameSource = mediaCapture.FrameSources.Values.FirstOrDefault(source => source.Info.MediaStreamType == MediaStreamType.VideoRecord);
_frameReader = await mediaCapture.CreateFrameReaderAsync(frameSource, MediaEncodingSubtypes.Argb32);
_frameReader.FrameArrived += FrameReader_FrameArrived;
await _frameReader.StartAsync();

如下方所示,监听FrameArrived,使用Windows.UI.Xaml.Media.Imaging.BitmapImage渲染展示(仅用于展示,延迟很高):

private async void FrameReader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    var frame = sender.TryAcquireLatestFrame();
    if (frame != null)
    {
        var bitmap = frame.VideoMediaFrame?.SoftwareBitmap;
        if (bitmap != null)
        {
            // 在这里对每一帧进行处理
            await Dispatcher.InvokeAsync(async () =>
            {
                var bitmapImage = await ConvertSoftwareBitmapToBitmapImageAsync(bitmap);
                _captureImage.Source = bitmapImage;
            });
        }
    }
}

如需要将SoftwareBitmap转为buffer字节数据,可以按如下处理:

public async Task<byte[]> SoftwareBitmapToByteArrayAsync(SoftwareBitmap softwareBitmap)
{
    // 使用InMemoryRandomAccessStream来存储图像数据
    using var stream = new InMemoryRandomAccessStream();
    // 创建位图编码器
    var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
    // 转换为BGRA8格式,如果当前格式不同
    var bitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
    encoder.SetSoftwareBitmap(bitmap);
    await encoder.FlushAsync();
    bitmap.Dispose();

    // 读取字节数据
    using var reader = new DataReader(stream.GetInputStreamAt(0));
    byte[] byteArray = new byte[stream.Size];
    await reader.LoadAsync((uint)stream.Size);
    reader.ReadBytes(byteArray);

    return byteArray;
}

以上,MediaCapture能实现摄像头显示及录制相关功能。

MediaCapture代码Demo详见Github:kybs00/CameraCaptureDemo: 摄像头预览、捕获DEMO (github.com)

这是我个人电脑本地监听帧转Image显示的延迟,差不多180ms:

其它

OpenCvSharp是OpenCV在C#环境中的包装,提供跨平台的计算机视觉和图像处理功能 shimat/opencvsharp: OpenCV wrapper for .NET (github.com)2K视频较为流畅,4K视频延迟较低但显示效果较差

其它的如EmguCV是另一个基于OpenCV的C#包装组件库,具有与OpenCVSharp相同的强大功能 emgucv/emgucv: Emgu CV (github.com)

DirectShow.NET,提供对DirectShow API的托管包装,使得在.NET框架中可以直接使用DirectShow的强大功能来进行视频捕获和处理 pauldotknopf/DirectShow.NET。DirectShow本身性能较好,但DirectShow.NET作为托管包装,性能会受一定影响。延迟效果待验证

此处就不一一例写Demo了

我们看看性能数据,4K屏设备+4K摄像头,通过本地摄像头预览显示。借用组内小伙伴建凯大佬对各方案的延时统计数据:

验证了下MediaCaptre的延时与系统相机差不多。通过任务管理器我们可以看到,系统相机CPU占用3%,但GPU是15%。

使用了硬件加速,性能方面很不错,所以摄像头采集推荐MediaCaptre方案

值得一提的是,公司大屏HDMI采集卡信号Hdmi Record即6911龙讯固件,采用MediaCapture采集画面会稳定很多,减少了黑屏、粉屏的概率。从这点也说明Windows系统相机的原生实现方案,兼容性更好

另外,这里验证的方案都是针对4K摄像头,如果是8K摄像头,其性能要求更高了,后面单独介绍

最后

恭喜作者通过面试,如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值