机器视觉C#调用相机实战:从USB摄像头到海康工业相机(WinForms & WPF)
在工业自动化、医疗影像或智能质检等场景中,调用相机是开发者绕不开的核心技能。无论你面对的是普通的USB摄像头,还是专业的海康工业相机,C#与WinForms/WPF都能提供高效解决方案。本文将从环境配置到代码实现,带你一步步掌握两种相机的调用方法,并附上避坑指南与性能优化建议。
环境准备:工具与依赖
开始前,请确保开发环境满足以下条件:
- 开发工具:Visual Studio 2022 / 2019
- 目标框架:.NET 6.0 或 .NET Framework 4.7.2 及以上
- NuGet包:通用USB相机推荐使用
或OpenCvSharp4.Windows(本文选用AForge.NET)OpenCvSharp - 海康相机SDK:前往海康机器人官网下载
(机器视觉软件),安装后引用其SDKMVS
⚠️ 如果你是第一次接触工业相机,建议先熟悉C#基础语法和WinForms/WPF的控件使用。此外,了解一些Python或C++的OpenCV经验也会帮助你更快上手。
第一部分:通用USB相机调用(基于OpenCvSharp)
普通USB摄像头通过DirectShow驱动被系统识别。使用 库,我们可以用几行代码实现实时画面捕获。OpenCvSharp
1. 核心思路
OpenCV的 类封装了DirectShow接口,只需传入摄像头ID(通常为0, 1, 2…)即可打开设备。在C#中,我们通过OpenCvSharp调用这些底层能力。VideoCapture
2. WinForms实现(实时显示)
步骤一:安装NuGet
在项目中安装
Install-Package OpenCvSharp4.Windows。步骤二:界面设计
拖拽一个 用于显示画面,一个 PictureBox 用于启动。Button
步骤三:后台代码
参考以下代码实现相机初始化与帧循环:
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Threading;
using System.Windows.Forms;
namespace USBCamera_WinForms
{
public partial class Form1 : Form
{
private VideoCapture _capture;
private Thread _captureThread;
private bool _isRunning = false;
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
if (_isRunning) return;
// 0 代表第一个 USB 摄像头,如果笔记本自带摄像头通常是 0,外接可能是 1
_capture = new VideoCapture(0);
if (!_capture.IsOpened())
{
MessageBox.Show("无法打开摄像头,请检查设备连接。");
return;
}
_isRunning = true;
_captureThread = new Thread(CaptureFrame);
_captureThread.IsBackground = true;
_captureThread.Start();
}
private void CaptureFrame()
{
while (_isRunning)
{
using (Mat frame = new Mat())
{
_capture.Read(frame);
if (frame.Empty()) continue;
// 将 OpenCV 的 Mat 转换为 WinForms 的 Bitmap
using (Bitmap bitmap = BitmapConverter.ToBitmap(frame))
{
// 跨线程更新 UI
pictureBox1.Invoke(new Action(() =>
{
pictureBox1.Image?.Dispose(); // 释放旧图像防止内存泄漏
pictureBox1.Image = new Bitmap(bitmap);
}));
}
}
Thread.Sleep(30); // 控制帧率约 30fps
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_isRunning = false;
_captureThread?.Join(1000);
_capture?.Release();
_capture?.Dispose();
}
}
}✅ 注意:在循环中务必释放旧的Mat对象,否则内存会持续飙升。这与Java或Go中手动管理资源类似,C#的垃圾回收虽然自动,但高频图像处理仍需谨慎。
3. WPF实现
WPF不能直接接收 ,需要借助 Bitmap 实现高性能渲染。WriteableBitmap
步骤一:界面XAML
<Window x:Class="USBCamera_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="USB相机 WPF版" Height="450" Width="800">
<Grid>
<Image x:Name="videoImage" Stretch="Uniform" Background="Black"/>
<Button x:Name="btnStart" Content="打开相机" Width="100" Height="30"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="10" Click="BtnStart_Click"/>
</Grid>
</Window>步骤二:后台逻辑
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
namespace USBCamera_WPF
{
public partial class MainWindow : Window
{
private VideoCapture _capture;
private Thread _captureThread;
private volatile bool _isRunning = false;
public MainWindow()
{
InitializeComponent();
}
private void BtnStart_Click(object sender, RoutedEventArgs e)
{
if (_isRunning) return;
_capture = new VideoCapture(0);
if (!_capture.IsOpened())
{
MessageBox.Show("无法打开摄像头");
return;
}
_isRunning = true;
_captureThread = new Thread(CaptureCamera);
_captureThread.IsBackground = true;
_captureThread.Start();
}
private void CaptureCamera()
{
while (_isRunning)
{
using (Mat frame = new Mat())
{
_capture.Read(frame);
if (frame.Empty()) continue;
// 关键点:Mat 转 WriteableBitmap (WPF 专用)
var bitmap = frame.ToWriteableBitmap();
// 调度到 UI 线程更新
Application.Current.Dispatcher.Invoke(() =>
{
videoImage.Source = bitmap;
});
}
Thread.Sleep(30);
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
_isRunning = false;
_captureThread?.Join(1000);
_capture?.Release();
base.OnClosing(e);
}
}
}如果追求更现代化的架构,可以考虑使用MVVM模式,将相机逻辑封装在ViewModel中。这在大型项目中尤其有用,类似TypeScript的Angular或React的状态管理模式。
第二部分:海康工业相机调用(基于MVS SDK)
海康工业相机(网口或USB3.0)功能强大,但必须依赖官方SDK。下面介绍核心流程。
1. 环境配置
- 下载并安装MVS(机器视觉软件)
- 在项目中添加对
的引用(路径通常在MvCameraControl.Net.dll)C:\Program Files (x86)\MVS\Development\Bin\ - 网口相机请确保PC与相机IP在同一网段
2. 核心调用步骤(WinForms & WPF通用)
以WinForms为例,展示枚举设备、打开相机、注册回调获取图像:
using MvCameraControl;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace HikCamera_Demo
{
public partial class FormHik : Form
{
private MyCamera _camera = null;
private bool _isGrabbing = false;
public FormHik()
{
InitializeComponent();
}
private void btnEnum_Click(object sender, EventArgs e)
{
// 1. 枚举设备
MyCamera.MV_CC_DEVICE_INFO_LIST deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
int nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref deviceList);
if (nRet != MyCamera.MV_OK || deviceList.nDeviceNum == 0)
{
MessageBox.Show("未发现相机!");
return;
}
// 假设取第一个设备
MyCamera.MV_CC_DEVICE_INFO deviceInfo = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(deviceList.pDeviceInfo[0], typeof(MyCamera.MV_CC_DEVICE_INFO));
// 2. 创建设备实例
_camera = new MyCamera();
nRet = _camera.MV_CC_CreateDevice_NET(ref deviceInfo);
if (nRet != MyCamera.MV_OK)
{
MessageBox.Show("创建设备失败");
return;
}
// 3. 连接设备
nRet = _camera.MV_CC_OpenDevice_NET();
if (nRet != MyCamera.MV_OK)
{
MessageBox.Show("连接失败");
return;
}
// 4. 设置触发模式为连续(自动出图)
_camera.MV_CC_SetEnumValue_NET("TriggerMode", 0); // 0: 关, 1: 开
// 5. 注册图像回调
_camera.MV_CC_RegisterImageCallBack_NET(ImageCallback, IntPtr.Zero);
// 6. 开始采集
_camera.MV_CC_StartGrabbing_NET();
_isGrabbing = true;
btnEnum.Enabled = false;
MessageBox.Show("相机启动成功");
}
// 图像回调函数(在 SDK 子线程中执行)
private void ImageCallback(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pUser)
{
if (!_isGrabbing) return;
try
{
// 将原始数据转换为 Bitmap
// 根据像素格式进行转换,这里是简单示例(假设是 BGR8)
byte[] buffer = new byte[pFrameInfo.nFrameLen];
Marshal.Copy(pData, buffer, 0, (int)pFrameInfo.nFrameLen);
// 根据宽高创建 Bitmap
Bitmap bitmap = new Bitmap((int)pFrameInfo.nWidth, (int)pFrameInfo.nHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat);
Marshal.Copy(buffer, 0, bmpData.Scan0, (int)pFrameInfo.nFrameLen);
bitmap.UnlockBits(bmpData);
// 更新 UI(注意跨线程)
pictureBox1.Invoke(new Action(() =>
{
pictureBox1.Image?.Dispose();
pictureBox1.Image = bitmap;
}));
}
catch (Exception ex)
{
Console.WriteLine("回调异常:" + ex.Message);
}
}
private void FormHik_FormClosing(object sender, FormClosingEventArgs e)
{
if (_camera != null)
{
_isGrabbing = false;
_camera.MV_CC_StopGrabbing_NET();
_camera.MV_CC_CloseDevice_NET();
_camera.MV_CC_DestroyDevice_NET();
}
}
}
}
⚠️ 注意:
海康 SDK 的像素格式(PixelFormat)很多(如 Mono8、BayerRG8、RGB24),必须根据相机实际设置进行转换。回调函数是非 UI 线程,更新图像时必须使用 或 。记得在项目属性中勾选“允许不安全代码”(如果使用指针操作)。
⚠️ 海康相机的回调函数运行在独立线程中,切勿在回调中直接操作UI。建议使用缓冲机制(如ConcurrentQueue)将图像帧传递给UI线程。这一设计理念与C++中的生产者-消费者模式一脉相承。
[AFFILIATE_SLOT_1]参考文档与资源
- OpenCV官方文档:https://docs.opencv.org/
- OpenCvSharp GitHub:https://github.com/shimat/opencvsharp
- 海康机器人MVS开发指南:安装MVS后,在
目录下有详细的《SDK开发指南.pdf》Development\Doc - 替代方案:如果不使用OpenCV,可选用
或AForge.NET(UWP),但OpenCV的跨平台性和功能完整性更强MediaCapture
对于追求极致性能的开发者,可以结合C++编写底层图像处理模块,通过P/Invoke或C++/CLI桥接调用。这种混合编程策略在Go语言中也有类似实践(通过cgo调用C库)。
总结与避坑指南
1. 框架选择建议
- WinForms:开发速度快,适合简单工业软件原型,但界面美观度稍逊
- WPF:界面设计灵活,支持MVVM模式,适合复杂交互的桌面应用
2. 常见问题
- 相机打开失败:检查相机是否被其他软件占用(如系统相机应用、其他调试程序)
- 内存泄漏:实时显示时,务必释放旧的
或Bitmap对象WriteableBitmap - 海康相机找不到:网口相机关闭防火墙、固定IP;USB相机通过MVS工具卸载系统
驱动,安装厂商驱动UVC
3. 性能优化
- 使用
缓冲机制处理回调,避免UI渲染阻塞相机线程Queue - 高分辨率相机建议先缩放再显示,减轻UI压力
- 考虑使用硬件加速(如DirectX或CUDA)进行图像处理,这在Python的OpenCV中也很常见
本文从USB摄像头到海康工业相机,覆盖了C#在WinForms和WPF中的完整调用流程。无论你是刚接触机器视觉的新手,还是正在迁移项目的资深开发者,希望这份指南能帮你快速落地。如果你在实现过程中遇到问题,欢迎在评论区交流!Happy Coding!
InvokeDispatcher
浙公网安备 33010602011771号