🎥 机器视觉C# 调用相机:从 USB 摄像头到海康工业相机(WinForms & WPF)
📝 前言
在工业自动化、医疗影像或简单软件开发中,调用摄像头是一个绕不开的话题。在项目中同时遇到了两种需求:
- 通用 USB 相机:如笔记本自带摄像头、普通免驱 USB 摄像头。
- 专业工业相机:如海康威视(Hikvision)的机器视觉相机,需要调用厂商 SDK。
本文将带你分别使用 WinForms 和 WPF 框架,通过 C# 语言实现这两种相机的调用。文章不仅会提供可直接运行的代码,还会附带详细的思路解析和常见踩坑点,希望能为你的项目提供一份可靠的参考。
🛠 环境准备
- 开发工具:Visual Studio 2022 / 2019
- 目标框架:.NET 6.0 / .NET Framework 4.7.2 及以上
- NuGet 包 :
- 通用 USB:
OpenCvSharp4.Windows或AForge.NET(本文使用OpenCvSharp) - 海康相机:需前往 海康机器人官网 下载
MVS(机器视觉软件)并引用其 SDK。
- 通用 USB:
第一部分:通用 USB 相机调用(基于 OpenCvSharp)
对于普通的 USB 摄像头,Windows 系统通过 DirectShow 驱动识别。我们可以使用 OpenCvSharp 库来轻松操作。
1. 核心思路
OpenCV 的 VideoCapture 类封装了 DirectShow,只需传入摄像头 ID(通常为 0, 1, 2...)即可打开设备。
2. WinForms 实现(实时显示)
步骤一:安装 NuGet
bash
Install-Package OpenCvSharp4.Windows
步骤二:界面设计
拖拽一个 PictureBox 用于显示画面,一个 Button 用于启动。
步骤三:后台代码
csharp
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();
}
}
}
3. WPF 实现
WPF 与 WinForms 不同,不能直接接收 Bitmap。我们需要借助 WriteableBitmap 来实现高性能渲染。
步骤一:界面 XAML
xml
<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>
步骤二:后台逻辑
csharp
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);
}
}
}
第二部分:海康工业相机调用(基于 MVS SDK)
海康的工业相机(如网口或 USB3.0 接口)功能强大,但必须依赖官方 SDK。下面是调用海康相机的基本流程。
1. 环境配置
- 下载并安装 MVS(机器视觉软件)。
- 安装完成后,在项目中添加对
MvCameraControl.Net.dll的引用。- 路径通常在:
C:\Program Files (x86)\MVS\Development\Bin\
- 路径通常在:
- 如果是网口相机,请确保 PC 和相机 IP 在同一网段。
2. 核心调用步骤(WinForms & WPF 通用)
这里以 WinForms 为例,展示如何枚举设备、打开相机、注册回调获取图像。
代码示例
csharp
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 线程,更新图像时必须使用
Invoke或Dispatcher。- 记得在项目属性中勾选"允许不安全代码"(如果使用指针操作)。
📚 参考文档与资源
- OpenCV 官方文档 :https://docs.opencv.org/
- OpenCvSharp GitHub :https://github.com/shimat/opencvsharp
- 海康机器人 MVS 开发指南 :安装 MVS 后,在
Development\Doc目录下有详细的《SDK 开发指南.pdf》。 - DirectShow 替代方案 :如果不想用 OpenCV,也可以使用
AForge.NET或MediaCapture(UWP),但 OpenCV 的跨平台性和功能完整性更强。
🧩 总结与避坑指南
1. 框架选择建议
- WinForms:开发速度快,适合简单的工业软件原型,但界面美观度稍逊。
- WPF:界面设计灵活,支持 MVVM 模式,适合复杂交互的桌面应用。
2. 常见问题
- 相机打开失败:检查相机是否被其他软件占用(如系统相机应用、其他调试程序)。
- 内存泄漏 :在实时显示时,务必释放旧的
Bitmap或WriteableBitmap对象,否则内存会持续飙升。 - 海康相机找不到 :
- 网口相机:关闭防火墙,固定 IP。
- USB 相机:安装 MVS 自带的驱动(通过 MVS 工具卸载系统的
UVC驱动,安装厂商驱动)。
3. 性能优化
- 使用
Queue缓冲机制处理回调,避免 UI 渲染阻塞相机回调线程。 - 对于高分辨率相机,建议将图像缩放后再显示,减轻 UI 压力。
🚀 结语
本文覆盖了从简单的 USB 摄像头到专业的海康工业相机在 C# 中的调用方法,并分别给出了 WinForms 和 WPF 的示例。希望这篇博客能帮助你快速上手相机的开发。
如果你在实现过程中遇到任何问题,欢迎在评论区留言交流!
Happy Coding! 🎉