WPF使用MediaCapture开发相机应用(三、相机拍照)

news/2025/10/21 15:09:34/文章来源:https://www.cnblogs.com/yy250/p/19155436

上期弄好了相机的预览功能,现在可以进行拍照了。对上期有疑惑的点这里。

MediaCapture的教程是用UWP写的,教程里拍照是用MediaCapture.CapturePhotoToStorageFileAsync方法,老实说我研究了好久都没搞明白WPF什么创建IStorageFile或者IRandomAccessStream对象,最终放弃了使用CapturePhotoToStorageFileAsync,选择了低快门的PrepareLowLagPhotoCaptureAsync方法。代码如下:

private async void Photo_Click(object sender, RoutedEventArgs e)
{if (Capture is null) return;//控制设备低快门拍照。其实教程用的是CapturePhotoToStorageFileAsync,但那是UWP的内容,我没找到在WPF上使用的方法。var iep = ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12);var ahc = await Capture.PrepareLowLagPhotoCaptureAsync(iep);var res = await ahc.CaptureAsync();var bmp = res.Frame.SoftwareBitmap;await ahc.FinishAsync();//读取缓冲区using var m = bmp.LockBuffer(BitmapBufferAccessMode.Read);using var n = m.CreateReference();var buf = new Windows.Storage.Streams.Buffer(n.Capacity);var data = new byte[n.Capacity];bmp.CopyToBuffer(buf);using var reader = DataReader.FromBuffer(buf);reader.ReadBytes(data);//保存文件var sfd = new SaveFileDialog() {Filter = "图片|*.jpg;",FileName = $"{DateTimeOffset.Now.Ticks}.jpg",DefaultDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)};if (sfd.ShowDialog() != true) return;//using var mat = new Mat(bmp.PixelHeight * 3 / 2, bmp.PixelWidth, MatType.CV_8UC1, data); 不知道啥时候起不能这么写了,吐槽一下。using var nv12 = new Mat(bmp.PixelHeight * 3 / 2, bmp.PixelWidth, MatType.CV_8UC1);Marshal.Copy(data, 0, nv12.Data, data.Length);using var bgr = new Mat();Cv2.CvtColor(nv12, bgr, ColorConversionCodes.YUV2BGR_NV12);bgr.SaveImage(sfd.FileName);
}

一般来说到这一步就算结束了,但考虑到很多时候领导都会提这么个需求:延时要低,清晰度要高,偏偏机器性能还不行。这种时候我们开发就需要在预览时用较低像素的画面糊弄一下,然后拍照时输出高像素的图片。
之前初始化MediaCapture时使用的是只读模式,现在需要改成控制模式:

var capture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{SourceGroup = device,SharingMode = MediaCaptureSharingMode.ExclusiveControl,//改了这里。换成可控制设备。StreamingCaptureMode = StreamingCaptureMode.Video,MemoryPreference = MediaCaptureMemoryPreference.Cpu,
};
await capture.InitializeAsync(settings);

然后拍照(调用PrepareLowLagPhotoCaptureAsync)之前加上这两句:

var max = Capture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo).OfType<VideoEncodingProperties>().OrderByDescending(p => p.Width * p.Height).First();
await Capture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, max);

上面是正统方法,但这么写的话,从点击按钮到弹出保存文件的窗口要花上大半秒,延时非常明显。我仔细研究后发现,不需要更改设备,只需要设置传入的ImageEncodingProperties的宽高就好。我也不确定是不是真的变清晰了,肉眼看不出来。

var max = Capture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo).OfType<VideoEncodingProperties>().OrderByDescending(p => p.Width * p.Height).First();
//await Capture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, max);
//控制设备低快门拍照。其实教程用的是CapturePhotoToStorageFileAsync,但那是UWP的内容,我没找到在WPF上使用的方法。
var iep = ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12);
iep.Width = max.Width;
iep.Height = max.Height;
var ahc = await Capture.PrepareLowLagPhotoCaptureAsync(iep);

可以看到,预览和照片是两种不同的像素
image

收工,下面贴出完整的代码:

using Microsoft.Win32;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System.DirectoryServices.ActiveDirectory;
using System.IO;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Media.Capture;
using Windows.Media.Capture.Frames;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using Window = System.Windows.Window;namespace VideoCapture
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{private MediaFrameReader? FrameReader = null;private MediaCapture? Capture = null;public MainWindow(){InitializeComponent();}protected override void OnSourceInitialized(EventArgs e){base.OnSourceInitialized(e);Task.Run(CaptureInit);}private async Task CaptureInit(){//查找摄像头设备,这里仅作示例,只取第一个。实际开发请根据实际情况变通。var device = (await MediaFrameSourceGroup.FindAllAsync()).FirstOrDefault();if (device is null) return;//初始化 MediaCapture 对象var capture = new MediaCapture();var settings = new MediaCaptureInitializationSettings(){SourceGroup = device,SharingMode = MediaCaptureSharingMode.ExclusiveControl,//控制设备。StreamingCaptureMode = StreamingCaptureMode.Video,MemoryPreference = MediaCaptureMemoryPreference.Cpu,//这里不要用auto,不然可能会读不到帧数据。};await capture.InitializeAsync(settings);//获取预览流foreach (MediaFrameSource source in capture.FrameSources.Values){MediaFrameSourceKind kind = source.Info.SourceKind;if (kind != MediaFrameSourceKind.Color) continue;//为了方便,这里只要Nv12的,实际开发可根据情况变通if (source.CurrentFormat.Subtype.ToUpper() != "NV12"){//查找指定的格式foreach(var f in source.SupportedFormats){if (f.Subtype.ToUpper() != "NV12") continue;//尝试设置格式try { await source.SetFormatAsync(f); }catch { continue; }}}//创建帧数据读取设备MediaFrameReader frameReader = await capture.CreateFrameReaderAsync(source, MediaEncodingSubtypes.Nv12);frameReader.FrameArrived += Reader_FrameArrived;MediaFrameReaderStartStatus status = await frameReader.StartAsync();if (status == MediaFrameReaderStartStatus.Success){FrameReader = frameReader;Capture = capture;break;}//没能正确创建,将其释放掉await frameReader.StopAsync();frameReader.Dispose();}}/// <summary>/// 帧数据缓冲区/// </summary>Windows.Storage.Streams.Buffer FrameBuf { get; set; }/// <summary>/// 帧数据/// </summary>public byte[] FrameData { get; private set; }/// <summary>/// NV12帧数据/// </summary>Mat FrameNV12 { get; set; }/// <summary>/// Bgr帧数据/// </summary>Mat FrameBgr { get; set; }/// <summary>/// 捕获帧/// </summary>private void Reader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args){var frame = sender.TryAcquireLatestFrame()?.VideoMediaFrame?.SoftwareBitmap;if (frame is null) return;//创建缓冲if (FrameNV12 is null || FrameNV12.Width != frame.PixelWidth || FrameNV12.Height != frame.PixelHeight){using var lockbuf = frame.LockBuffer(Windows.Graphics.Imaging.BitmapBufferAccessMode.Read);if (lockbuf is null) return;using var n = lockbuf.CreateReference();var t = lockbuf.GetPlaneDescription(0);FrameBuf = new Windows.Storage.Streams.Buffer(n.Capacity);FrameNV12 = new Mat(frame.PixelHeight * 3 / 2, frame.PixelWidth, MatType.CV_8UC1);FrameBgr = new Mat();}//读取数据frame.CopyToBuffer(FrameBuf);if (FrameData is null || FrameData.Length != FrameBuf.Length) FrameData = new byte[FrameBuf.Length];using var reader = DataReader.FromBuffer(FrameBuf);reader.ReadBytes(FrameData);//NV12转Rbg24Marshal.Copy(FrameData, 0, FrameNV12.Data, FrameData.Length);Cv2.CvtColor(FrameNV12, FrameBgr, ColorConversionCodes.YUV2BGR_NV12);Dispatcher.InvokeAsync(() => RendererFrame(FrameBgr, frame.PixelWidth, frame.PixelHeight));}/// <summary>/// 渲染帧/// </summary>private void RendererFrame(Mat bgr, int width, int height){if (bgr is null || width <= 0 || height <= 0 || FrameBgr.Width != width || FrameBgr.Height != height) return;if (FrameImage.Source is not WriteableBitmap bmp || bmp.PixelWidth != width || bmp.PixelHeight != height){bmp = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, default);FrameImage.Source = bmp;}WriteableBitmapConverter.ToWriteableBitmap(bgr, bmp);//显示当前预览像素,可删除Rel.Text = $"{width}x{height}";}/// <summary>/// 拍照/// </summary>private async void Photo_Click(object sender, RoutedEventArgs e){if (Capture is null) return;//预览可以使用较小的清晰度以保证流畅,但照片需要设置最高的像素。var max = Capture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo).OfType<VideoEncodingProperties>().OrderByDescending(p => p.Width * p.Height).First();//await Capture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, max);//控制设备低快门拍照。其实教程用的是CapturePhotoToStorageFileAsync,但那是UWP的内容,我没找到在WPF上使用的方法。var iep = ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12);iep.Width = max.Width;iep.Height = max.Height;var ahc = await Capture.PrepareLowLagPhotoCaptureAsync(iep);var res = await ahc.CaptureAsync();var bmp = res.Frame.SoftwareBitmap;await ahc.FinishAsync();//读取缓冲区using var m = bmp.LockBuffer(BitmapBufferAccessMode.Read);using var n = m.CreateReference();var buf = new Windows.Storage.Streams.Buffer(n.Capacity);var data = new byte[n.Capacity];bmp.CopyToBuffer(buf);using var reader = DataReader.FromBuffer(buf);reader.ReadBytes(data);//保存文件var sfd = new SaveFileDialog() {Filter = "图片|*.jpg;",FileName = $"{DateTimeOffset.Now.Ticks}.jpg",DefaultDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)};if (sfd.ShowDialog() != true) return;//using var mat = new Mat(bmp.PixelHeight * 3 / 2, bmp.PixelWidth, MatType.CV_8UC1, data); 不知道啥时候起不能这么写了,吐槽一下。using var nv12 = new Mat(bmp.PixelHeight * 3 / 2, bmp.PixelWidth, MatType.CV_8UC1);Marshal.Copy(data, 0, nv12.Data, data.Length);using var bgr = new Mat();Cv2.CvtColor(nv12, bgr, ColorConversionCodes.YUV2BGR_NV12);bgr.SaveImage(sfd.FileName);}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/942306.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年磨粉机厂家权威推荐榜:雷蒙磨粉机/环辊磨粉机/摆式磨粉机/矿石磨粉机/超细磨粉机/高压磨粉机,专业实力与高效生产之选

2025年磨粉机厂家权威推荐榜:雷蒙磨粉机/环辊磨粉机/摆式磨粉机/矿石磨粉机/超细磨粉机/高压磨粉机,专业实力与高效生产之选 在工业制造领域,磨粉设备作为物料加工的核心装备,其技术水平直接影响着生产效率和产品质…

我的第一份开源贡献:小米工程师程赛的社区之旅

打开链接点亮社区Star,照亮技术的前进之路。每一个点赞,都是社区技术大佬前进的动力Github 地址: https://github.com/secretflow/secretflow他是从校园走进开源世界的年轻工程师; 他在小米工作,却把开源当成生活…

2025陶瓷过滤机实力厂家推荐,铜陵杰达机械专注固液分离设备制造

2025陶瓷过滤机实力厂家推荐,铜陵杰达机械专注固液分离设备制造 固液分离领域的技术挑战与创新突破 在当前的工业生产中,固液分离设备作为关键环节,面临着诸多技术挑战。根据行业数据显示,传统过滤设备在处理微细颗…

2025信息流代运营公司推荐:线尚网络专注效果营销与品牌增长

2025信息流代运营公司推荐:线尚网络专注效果营销与品牌增长 信息流代运营领域面临的技术挑战 随着数字营销进入深水区,信息流代运营行业正面临前所未有的技术挑战。据行业数据显示,超过67%的企业在信息流广告投放中…

2025冷链解冻设备厂家推荐广东科恩,专业定制高湿静电解冻方案

2025冷链解冻技术新突破:专业定制高湿静电解冻方案引领行业变革 在食品工业快速发展的今天,解冻设备作为冷链环节中的重要组成部分,其技术水平直接影响着食品品质与安全。随着2025年的临近,冷链解冻设备行业正迎来…

SecureCRT 批量创建会话-cnblog

SecureCRT 批量创建会话 前言: 在使用CRT的过程中,如果需要创建大量的会话连接 如:使用telnet连接ensp的网络设备 如:一次性需要连接多台linux主机 开篇: 下载SecureCRT:https://www.vandyke.com/download/ 这里…

2025干燥设备厂家权威推荐:常州亿干专业定制实验室喷雾与真空耙式干燥机

2025干燥设备厂家权威推荐:常州亿干专业定制实验室喷雾与真空耙式干燥机 一、干燥设备行业面临的技术挑战 在当今工业生产领域,干燥设备作为关键工艺装备,其技术水平直接影响着产品质量和生产效率。小型喷雾干燥机、…

2025磨粉机厂家权威推荐:海城八里镇机械厂专注矿石超细研磨设备制造

2025磨粉机厂家权威推荐:海城八里镇机械厂专注矿石超细研磨设备制造 行业技术挑战与创新需求 在当前的工业制造领域,磨粉设备的技术革新正面临着前所未有的挑战。随着矿产资源的深度开发和精细化加工需求的提升,传统…

Vue技术之Vxe-Table的虚拟滚动

虚拟滚动 纵向虚拟滚动:scroll-y 横向虚拟滚动:scroll-x 1.首先留意项目中使用的版本,我这边在项目用的是"vxe-pc-ui": "4.0.95"版本"vxe-table": "4.7.65"版本 所以scrol…

实用指南:一次借助ChatGPT抵御恶意攻击的经历,为个人服务器添加自动防御系统Fail2ban

实用指南:一次借助ChatGPT抵御恶意攻击的经历,为个人服务器添加自动防御系统Fail2banpre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important;…

Docker补充

Docker知识点学习。目录Docker实际应用docker 管理镜像操作容器操作Dockerfile命令学习一些Docker的图解 Docker实际应用 docker 管理 镜像操作搜索对应镜像docker serarch xxx查看现有镜像docker images docker imag…

【QNX】Socket ServerClient 源代码

1 Socket Server 源代码 服务端计划只启动一个线程,所以功能实现时使用了一些全局变量。1 int32_t skt_s_listen_fd{-1};2 pthread_t thr_server;3 struct sockaddr_un srv_addr;4 struct sockaddr_un cli_addr;5 std…

Linux环境--文件系统--动静态库

Linux环境--文件系统--动静态库2025-10-21 14:44 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !importa…

详细介绍:大模型落地的四大核心引擎:从技术突破到产业重构

详细介绍:大模型落地的四大核心引擎:从技术突破到产业重构pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Conso…

2025机电安装厂家推荐:太仓华芃专注工业设备安装,实力厂家可靠之选

2025机电安装厂家推荐:太仓华芃专注工业设备安装,实力厂家可靠之选 工业设备安装领域的技术挑战与行业现状 随着制造业向智能化、精密化方向快速发展,工业设备安装行业正面临着前所未有的技术挑战。据统计数据显示,…

EasyCVR视频汇聚平台GB28181级联异常排查:上级订阅信息无响应的根源解析

EasyCVR视频汇聚平台GB28181级联异常排查:上级订阅信息无响应的根源解析在视频监控平台的级联部署中,GB28181协议的稳定运行是保障上下级平台数据互通的核心。最近我们收到用户反馈:EasyCVR向下级平台级联时,状态显…

2025不锈钢酸洗钝化液实力厂家推荐,常州隆彦商贸环保型清洗解决方案

2025不锈钢酸洗钝化液实力厂家推荐,常州隆彦商贸环保型清洗解决方案 技术革新引领行业变革 在当前不锈钢表面处理领域,酸洗钝化工艺正面临前所未有的技术挑战。传统酸洗工艺普遍存在能耗高、污染严重、效率低下等问题…

2025 年半导体探针台厂家最新推荐榜:覆盖晶圆 / 高低温 / 射频测试领域,精选国产实力企业

引言 随着半导体产业向高精度、多场景方向快速发展,探针台作为芯片测试核心设备,其性能与服务直接影响企业研发效率与产品质量。当前市场中,探针台厂家数量繁杂,部分企业技术薄弱、服务滞后,难以适配新能源、光通…