机器视觉C# 调用相机:从 USB 摄像头到海康工业相机(WinForms & WPF)

🎥 机器视觉C# 调用相机:从 USB 摄像头到海康工业相机(WinForms & WPF)

📝 前言

在工业自动化、医疗影像或简单软件开发中,调用摄像头是一个绕不开的话题。在项目中同时遇到了两种需求:

  1. 通用 USB 相机:如笔记本自带摄像头、普通免驱 USB 摄像头。
  2. 专业工业相机:如海康威视(Hikvision)的机器视觉相机,需要调用厂商 SDK。

本文将带你分别使用 WinFormsWPF 框架,通过 C# 语言实现这两种相机的调用。文章不仅会提供可直接运行的代码,还会附带详细的思路解析和常见踩坑点,希望能为你的项目提供一份可靠的参考。


🛠 环境准备

  • 开发工具:Visual Studio 2022 / 2019
  • 目标框架:.NET 6.0 / .NET Framework 4.7.2 及以上
  • NuGet 包
    • 通用 USB:OpenCvSharp4.WindowsAForge.NET(本文使用 OpenCvSharp
    • 海康相机:需前往 海康机器人官网 下载 MVS(机器视觉软件)并引用其 SDK。

第一部分:通用 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. 环境配置

  1. 下载并安装 MVS(机器视觉软件)
  2. 安装完成后,在项目中添加对 MvCameraControl.Net.dll 的引用。
    • 路径通常在:C:\Program Files (x86)\MVS\Development\Bin\
  3. 如果是网口相机,请确保 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();
            }
        }
    }
}

⚠️ 注意

  1. 海康 SDK 的像素格式(PixelFormat)很多(如 Mono8、BayerRG8、RGB24),必须根据相机实际设置进行转换。
  2. 回调函数是非 UI 线程,更新图像时必须使用 InvokeDispatcher
  3. 记得在项目属性中勾选"允许不安全代码"(如果使用指针操作)。

📚 参考文档与资源

  1. OpenCV 官方文档https://docs.opencv.org/
  2. OpenCvSharp GitHubhttps://github.com/shimat/opencvsharp
  3. 海康机器人 MVS 开发指南 :安装 MVS 后,在 Development\Doc 目录下有详细的《SDK 开发指南.pdf》。
  4. DirectShow 替代方案 :如果不想用 OpenCV,也可以使用 AForge.NETMediaCapture(UWP),但 OpenCV 的跨平台性和功能完整性更强。

🧩 总结与避坑指南

1. 框架选择建议

  • WinForms:开发速度快,适合简单的工业软件原型,但界面美观度稍逊。
  • WPF:界面设计灵活,支持 MVVM 模式,适合复杂交互的桌面应用。

2. 常见问题

  • 相机打开失败:检查相机是否被其他软件占用(如系统相机应用、其他调试程序)。
  • 内存泄漏 :在实时显示时,务必释放旧的 BitmapWriteableBitmap 对象,否则内存会持续飙升。
  • 海康相机找不到
    • 网口相机:关闭防火墙,固定 IP。
    • USB 相机:安装 MVS 自带的驱动(通过 MVS 工具卸载系统的 UVC 驱动,安装厂商驱动)。

3. 性能优化

  • 使用 Queue 缓冲机制处理回调,避免 UI 渲染阻塞相机回调线程。
  • 对于高分辨率相机,建议将图像缩放后再显示,减轻 UI 压力。

🚀 结语

本文覆盖了从简单的 USB 摄像头到专业的海康工业相机在 C# 中的调用方法,并分别给出了 WinForms 和 WPF 的示例。希望这篇博客能帮助你快速上手相机的开发。

如果你在实现过程中遇到任何问题,欢迎在评论区留言交流!

Happy Coding! 🎉

相关推荐
极客智造3 小时前
WPF DataGrid 多选绑定 + 强类型命令回调 通用解决方案
wpf
Daydreamer .3 小时前
VisionMaster使用OpenCV发现的问题
opencv·c#·visionmaster
唐青枫13 小时前
C#.NET ReaderWriterLockSlim 深入解析:读写锁原理、升级锁与使用边界
c#·.net
ALex_zry19 小时前
C++高性能日志与监控系统设计
c++·unity·wpf
ghie90901 天前
C# WinForms 条形码生成器(含保存和打印预览功能)
开发语言·c#
蒙塔基的钢蛋儿1 天前
告别内存泄露与空指针:用C#与.NET 10开启STM32H7高性能单片机开发新纪元
stm32·c#·.net
ZoeJoy81 天前
C# Windows Forms 学生成绩管理器(StudentGradeManager)—— 方法重载、out、ref、params 参数示例
开发语言·c#
solicitous1 天前
历史与术语
学习·c#