资讯动态

C# WPF 实现摄像头视频流处理与实时标记

发布时间:2026/8/10 21:57:37 来源:尧图企业网站定制
1. 从零开始搭建WPF摄像头应用第一次用WPF做摄像头项目时我对着空白的Visual Studio界面发了半小时呆。后来才发现用C#操作摄像头就像用手机拍照一样简单——前提是得找对工具。这里推荐使用EmguCV这个神器它是OpenCV的.NET封装特别适合我们这种熟悉C#但不想碰C的开发者。先说说环境准备。打开NuGet包管理器搜索安装EmguCV的四个核心组件Emgu.CV.runtime.windows、Emgu.CV、Emgu.CV.Bitmap和Emgu.CV.UI。安装时要注意版本匹配我曾经因为混用3.4和4.5版本导致图像显示异常。安装完成后在MainWindow.xaml里放个Image控件命名为CameraImage这就是我们显示视频流的画布。// 初始化摄像头 using Emgu.CV; using Emgu.CV.Structure; Capture capture new Capture(0); // 0表示默认摄像头 Application.Idle ProcessFrame; // 每帧回调这段代码创建了一个摄像头捕获对象参数0表示使用第一个检测到的摄像头。如果你接入了多个摄像头可以尝试1、2等索引值。我在笔记本上测试时发现有些USB摄像头会被识别为第二个设备这时候改成1就能正常工作了。2. 实时视频流处理的核心机制视频流处理的本质就是连续处理静态帧图像。在ProcessFrame方法里我们通过QueryFrame获取当前帧void ProcessFrame(object sender, EventArgs e) { Mat frame capture.QueryFrame(); if (frame ! null) { ImageBgr, byte image frame.ToImageBgr, byte(); // 在这里添加图像处理代码 CameraImage.Source ToBitmapSource(image); } }这里有个性能陷阱要注意直接使用UI线程处理视频会导致界面卡顿。我后来改用Dispatcher.BeginInvoke异步更新UI帧率立即从15fps提升到30fpsApplication.Current.Dispatcher.BeginInvoke(new Action(() { CameraImage.Source ToBitmapSource(image); }));Mat对象是EmguCV的基础图像容器相当于OpenCV里的Mat。ToImageBgr, byte()将其转换为更方便操作的Image对象Bgr表示颜色格式byte是像素深度。记得在窗口关闭时释放资源protected override void OnClosed(EventArgs e) { capture.Dispose(); base.OnClosed(e); }3. 动态标记的实战技巧给视频添加标记就像在照片上涂鸦只不过我们的笔要跟着物体移动。假设我们要在检测到的人脸上画红框// 在ProcessFrame方法内添加 CascadeClassifier faceClassifier new CascadeClassifier(haarcascade_frontalface_default.xml); Rectangle[] faces faceClassifier.DetectMultiScale(image, 1.1, 10); foreach (Rectangle face in faces) { image.Draw(face, new Bgr(Color.Red), 3); }这里用到了OpenCV自带的级联分类器需要将xml文件放在bin目录下。实际项目中我遇到过三个坑1) xml文件忘记设置为始终复制2) 检测参数需要反复调整3) 矩形坐标超出图像边界。解决方法分别是在VS中右键xml文件→属性→复制到输出目录始终复制第三个参数minSize要根据摄像头分辨率调整我常用new Size(30,30)绘制前检查矩形范围if(face.X 0 face.Y 0 face.Width image.Width)更复杂的标记可以结合文字标注image.Draw($检测到{faces.Length}个人脸, new Point(10, 30), FontFace.HersheyComplex, 1.0, new Bgr(Color.Yellow));4. 性能优化与异常处理做实时视频处理就像在高速公路上换轮胎——必须又快又稳。这些优化技巧是我踩坑总结出来的多线程处理把图像处理放到后台线程通过锁机制保证线程安全private readonly object lockObj new object(); private ImageBgr, byte processedImage; void ProcessFrame(object sender, EventArgs e) { lock (lockObj) { Mat frame capture.QueryFrame(); Task.Run(() { var tempImage frame.ToImageBgr, byte(); // 处理图像... processedImage tempImage; }); } }帧率控制不是所有场景都需要30fps通过计时器控制处理频率DateTime lastProcessTime DateTime.MinValue; void ProcessFrame(object sender, EventArgs e) { if ((DateTime.Now - lastProcessTime).TotalMilliseconds 33) return; lastProcessTime DateTime.Now; // ...正常处理逻辑 }异常处理摄像头可能被其他程序占用需要友好提示try { capture new Capture(0); } catch (Exception ex) { MessageBox.Show($摄像头初始化失败{ex.Message}\n请检查是否被其他程序占用); this.Close(); }记得在finally块或窗口关闭事件中释放资源否则下次启动时可能报设备被占用错误。我在演示给客户看时就遇到过这个尴尬情况现在养成了写Dispose的好习惯。5. 扩展功能与实用案例给视频流添加标记只是开始真正的威力在于结合具体业务场景。去年我做了一个工厂质检系统在视频流上实时标记产品缺陷// 检测圆形物体 CircleF[] circles image.HoughCircles( new Gray(100), // 灰度阈值 new Gray(50), // 累加器阈值 2.0, // 分辨率 10.0, // 最小距离 5, // 最小半径 100 // 最大半径 )[0]; foreach (CircleF circle in circles) { if (circle.Radius 15) // 筛选小尺寸缺陷 { image.Draw(circle, new Bgr(Color.Red), 2); image.Draw(DEFECT, new Point((int)circle.Center.X, (int)circle.Center.Y), FontFace.HersheyPlain, 1.0, new Bgr(Color.Red)); } }另一个实用技巧是保存标记后的快照。我在Window类里添加了快捷键支持protected override void OnKeyDown(KeyEventArgs e) { if (e.Key Key.S Keyboard.Modifiers ModifierKeys.Control) { var dialog new SaveFileDialog(); if (dialog.ShowDialog() true) { processedImage?.ToBitmap().Save(dialog.FileName); } } }对于需要持久化标记数据的场景可以结合JSON序列化保存坐标信息ListRectangle savedRectangles new ListRectangle(); // 保存标记 savedRectangles.AddRange(faces); File.WriteAllText(markers.json, JsonConvert.SerializeObject(savedRectangles)); // 加载标记 var loaded JsonConvert.DeserializeObjectListRectangle( File.ReadAllText(markers.json));最后说说跨平台问题。虽然WPF是Windows技术但通过.NET Core和适当的抽象层核心处理逻辑可以复用。我在一个项目中就把图像处理部分抽成了独立类库客户端用WPF服务端用ASP.NET Core共享同样的检测算法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价