欢迎光临
我们一直在努力

C# + 工业相机 SDK 的采集代码模板

直接 dotnet run 的 .NET 8 控制台项目,用统一接口屏蔽了品牌差异——换相机只换实现类,业务代码不动。

项目结构

IndustrialCameraDemo/
├── ICamera.cs # 统一抽象 + FrameData 帧结构
├── BaslerPylonCamera.cs # ✅ Basler 实现(pylon/GenICam),可直接跑
├── HikvisionMvsCamera.cs # 海康/华睿 MVS 骨架(需补全官方 SDK)
├── ImageSaver.cs # PGM/PPM 保存,零第三方图像依赖
├── Program.cs # 完整流程:枚举→配参→连续→软触发→释放
└── README.md # 环境准备 + 调参 + 排坑清单

核心流程(记住这一套就够)

using ICamera camera = new BaslerPylonCamera();
camera.Open();

camera.SetExposureUs(10_000); // 曝光 10ms
camera.SetGain(0); // 增益 0(先靠曝光,增益放大噪声)
camera.SetContinuousMode(); // 或软触发 / 硬触发
camera.StartGrab();

var frame = camera.GrabOne(5000); // 取一帧
if (frame != null)
ImageSaver.SaveAuto(frame, "out.pgm"); // 保存

camera.StopGrab();
camera.Close();

软触发(外部传感器信号到达才拍,产线常见):

camera.SetSoftwareTriggerMode();
camera.StartGrab();
camera.SoftwareTriggerOnce(); // 相当于光电开关给了一个信号
var f = camera.GrabOne(5000);

三种触发模式都封装好了

方法

用途

SetContinuousMode()

连续采集,调试/预览用

SetSoftwareTriggerMode()

软触发,代码模拟外部信号

SetHardwareTriggerMode()

硬触发(Line1 外触发),产线实际用法

三个设计取舍

  • 主动取流(RetrieveResult)而非回调——新手更易理解、不会丢帧乱序;高帧率场景再换 ImageGrabbed 事件(README 已注明)
  • 用 PGM/PPM 存图——不依赖 System.Drawing(Linux 受限)和 OpenCV,跨平台直接看
  • 海康做了骨架而非硬写——MVS 无公共 NuGet、版本差异大,硬写易误导,所以保留标准结构 + 注释,你按官方文档填几行即可
  • 运行前必做

  • 装 Basler pylon Runtime,GigE 相机用 IP Configurator 设同网段 IP
  • 先用 pylon Viewer 确认能出图——排查"是相机问题还是代码问题"的第一步
  • dotnet restore && dotnet run,按提示选相机编号
  • 排坑速查(README 详细版)

    • 枚举不到 → GigE 查 IP 网段/防火墙,USB3 查是否插蓝色口
    • 能连但不出图 → 触发模式设成 On 却没给信号?先切连续模式验证
    • 图像全黑 → 镜头盖/光圈/光源,或曝光增益太小
    • 运动拖影 → 必须全局快门;或缩短曝光 + 频闪光源
    • 丢帧 → 开网卡巨帧、把处理挪到独立线程

    ⚠️ 说明:代码为工程模板,需配合真实相机 + pylon Runtime 才能出图;HikvisionMvsCamera.cs 为骨架需按官方 MVS SDK 补全。Basler.Pylon 包版本以实际可用为准。

    BaslerPylonCamera

    using System.Diagnostics;
    using Basler.Pylon;

    namespace IndustrialCameraDemo;

    /// <summary>
    /// Basler 相机实现(pylon SDK / GenICam)
    /// 支持 GigE Vision、USB3 Vision 等
    /// </summary>
    public sealed class BaslerPylonCamera : ICamera
    {
        private readonly Camera _camera;
        private bool _opened;
        private bool _grabbing;

        public string Name => _camera.CameraInfo[CameraInfoKey.FriendlyName];

        public bool IsOpen => _opened;

        public BaslerPylonCamera(string? serialOrName = null)
        {
            _camera = serialOrName is null ? new Camera() : new Camera(serialOrName);
        }

        /// <summary>枚举当前可用的 Basler 相机</summary>
        public static IReadOnlyList<string> Enumerate()
        {
            var list = new List<string>();
            foreach (var info in CameraFinder.Enumerate())
            {
                var name = info[CameraInfoKey.FriendlyName];
                var serial = info[CameraInfoKey.SerialNumber];
                list.Add($"{name} [{serial}]");
            }
            return list;
        }

        public void Open()
        {
            if (_opened) return;
            try
            {
                _camera.Open();
                _opened = true;
                Debug.WriteLine($"[相机] 已打开:{Name}");
            }
            catch (Exception ex)
            {
                throw new CameraException($"打开 Basler 相机失败:{ex.Message}", ex);
            }
        }

        public void Close()
        {
            if (!_opened) return;
            StopGrab();
            _camera.Close();
            _opened = false;
        }

        // ================= 参数配置 =================

        public void SetExposureUs(double microseconds)
        {
            EnsureOpen();
            // 新版本 pylon 用 ExposureTime;旧版本(已弃用)为 ExposureTimeAbs
            if (_camera.Parameters.Contains(PLCamera.ExposureTime))
                _camera.Parameters[PLCamera.ExposureTime].SetValue(microseconds);
            else
                _camera.Parameters[PLCamera.ExposureTimeAbs].SetValue(microseconds);
        }

        public void SetGain(double gain)
        {
            EnsureOpen();
            if (_camera.Parameters.Contains(PLCamera.Gain))
                _camera.Parameters[PLCamera.Gain].SetValue(gain);
        }

        public void SetRoi(int offsetX, int offsetY, int width, int height)
        {
            EnsureOpen();
            var p = _camera.Parameters;
            p[PLCamera.OffsetX].SetValue(offsetX);
            p[PLCamera.OffsetY].SetValue(offsetY);
            p[PLCamera.Width].SetValue(width);
            p[PLCamera.Height].SetValue(height);
        }

        public void SetContinuousMode()
        {
            EnsureOpen();
            _camera.Parameters[PLCamera.TriggerMode].SetValue(PLCamera.TriggerMode.Off);
            _camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
            Debug.WriteLine("[相机] 模式:连续采集");
        }

        public void SetSoftwareTriggerMode()
        {
            EnsureOpen();
            var p = _camera.Parameters;
            p[PLCamera.TriggerSelector].SetValue(PLCamera.TriggerSelector.FrameStart);
            p[PLCamera.TriggerMode].SetValue(PLCamera.TriggerMode.On);
            p[PLCamera.TriggerSource].SetValue(PLCamera.TriggerSource.Software);
            p[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
            Debug.WriteLine("[相机] 模式:软触发");
        }

        public void SetHardwareTriggerMode()
        {
            EnsureOpen();
            var p = _camera.Parameters;
            p[PLCamera.TriggerSelector].SetValue(PLCamera.TriggerSelector.FrameStart);
            p[PLCamera.TriggerMode].SetValue(PLCamera.TriggerMode.On);
            p[PLCamera.TriggerSource].SetValue(PLCamera.TriggerSource.Line1); // 外触发输入线,按接线调整
            p[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
            Debug.WriteLine("[相机] 模式:硬触发(Line1)");
        }

        // ================= 取流 =================

        public void StartGrab()
        {
            EnsureOpen();
            if (_grabbing) return;
            // GrabStrategy.OneByOne + ProvidedByUser:由我们主动 RetrieveResult 取帧,简单可控
            _camera.StreamGrabber.Start(GrabStrategy.OneByOne, GrabLoop.ProvidedByUser);
            _grabbing = true;
        }

        public void StopGrab()
        {
            if (!_grabbing) return;
            _camera.StreamGrabber.Stop();
            _grabbing = false;
        }

        public FrameData? GrabOne(int timeoutMs = 5000)
        {
            EnsureOpen();
            if (!_grabbing) StartGrab();

            try
            {
                using var result = _camera.StreamGrabber.RetrieveResult(timeoutMs, TimeoutHandling.ThrowException);
                if (!result.GrabSucceeded)
                {
                    Debug.WriteLine($"[相机] 采集失败:{result.ErrorCode} {result.ErrorDescription}");
                    return null;
                }

                return new FrameData
                {
                    Width = result.Width,
                    Height = result.Height,
                    PixelData = result.PixelData as byte[] ?? Array.Empty<byte>(),
                    PixelFormat = result.PixelTypeValue.ToString(),
                    FrameNumber = (ulong)result.ImageNumber,
                    TimestampNs = result.TimeStamp,
                    Native = null // IGrabResult 已在 using 中释放,此处不保留
                };
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[相机] 取帧异常:{ex.Message}");
                return null;
            }
        }

        public void SoftwareTriggerOnce()
        {
            EnsureOpen();
            _camera.Parameters[PLCamera.TriggerSoftware].Execute();
        }

        private void EnsureOpen()
        {
            if (!_opened) throw new CameraException("相机尚未打开,请先调用 Open()");
        }

        public void Dispose() => Close();
    }
    HikvisionMvsCamera

    using System.Diagnostics;

    // ⚠️ 本文件为「海康 / 华睿 MVS 相机」适配骨架。
    // 使用前:安装 MVS SDK(海康机器人官网),并在 csproj 中取消注释对
    // MvCameraControl.Net.dll 的引用(见 IndustrialCameraDemo.csproj 注释)。
    // 不同 MVS 版本 API 命名可能略有差异,请以官方文档为准。
    //
    // using MvCameraControl;

    namespace IndustrialCameraDemo;

    public sealed class HikvisionMvsCamera : ICamera
    {
        // private readonly MyCamera _camera = new();
        private bool _opened;
        private bool _grabbing;

        public string Name { get; } = "Hikvision/MVS Camera";
        public bool IsOpen => _opened;

        public void Open()
        {
            if (_opened) return;
            // 参考实现:
            // 1) 枚举设备
            // MyCamera.MV_CC_DEVICE_INFO_LIST stDevList = new();
            // MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref stDevList);
            // 2) 取第 0 台设备并创建句柄
            // MyCamera.MV_CC_DEVICE_INFO dev = (MyCamera.MV_CC_DEVICE_INFO)
            //     MyCamera.ByteToStruct(stDevList.pDeviceInfo[0], typeof(MyCamera.MV_CC_DEVICE_INFO));
            // _camera.MV_CC_CreateDevice_NET(ref dev);
            // 3) 打开
            // int ret = _camera.MV_CC_OpenDevice_NET(accessMode: (uint)MyCamera.MV_ACCESS_Exclusive, switchoverKey: 0);
            // if (ret != 0) throw new CameraException($"打开相机失败,错误码 0x{ret:x}");
            _opened = true;
            Debug.WriteLine("[相机] MVS 相机已打开(骨架代码,需按官方 SDK 补全)");
        }

        public void Close()
        {
            if (!_opened) return;
            StopGrab();
            // _camera.MV_CC_CloseDevice_NET();
            // _camera.MV_CC_DestroyDevice_NET();
            _opened = false;
        }

        public void SetExposureUs(double microseconds)
        {
            EnsureOpen();
            // _camera.MV_CC_SetFloatValue_NET("ExposureTime", (float)microseconds);
        }

        public void SetGain(double gain)
        {
            EnsureOpen();
            // _camera.MV_CC_SetFloatValue_NET("Gain", (float)gain);
        }

        public void SetRoi(int offsetX, int offsetY, int width, int height)
        {
            EnsureOpen();
            // _camera.MV_CC_SetIntValue_NET("OffsetX", (uint)offsetX);
            // _camera.MV_CC_SetIntValue_NET("OffsetY", (uint)offsetY);
            // _camera.MV_CC_SetIntValue_NET("Width", (uint)width);
            // _camera.MV_CC_SetIntValue_NET("Height", (uint)height);
        }

        public void SetContinuousMode()
        {
            EnsureOpen();
            // _camera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_TRIGGER_MODE_OFF);
        }

        public void SetSoftwareTriggerMode()
        {
            EnsureOpen();
            // _camera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_TRIGGER_MODE_ON);
            // _camera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_TRIGGER_SOURCE_SOFTWARE);
        }

        public void SetHardwareTriggerMode()
        {
            EnsureOpen();
            // _camera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_TRIGGER_MODE_ON);
            // _camera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_TRIGGER_SOURCE_LINE0);
        }

        public void StartGrab()
        {
            EnsureOpen();
            if (_grabbing) return;
            // int ret = _camera.MV_CC_StartGrabbing_NET();
            // if (ret != 0) throw new CameraException($"开始采集失败 0x{ret:x}");
            _grabbing = true;
        }

        public void StopGrab()
        {
            if (!_grabbing) return;
            // _camera.MV_CC_StopGrabbing_NET();
            _grabbing = false;
        }

        public FrameData? GrabOne(int timeoutMs = 5000)
        {
            EnsureOpen();
            if (!_grabbing) StartGrab();

            // 参考实现(主动取流):
            // MyCamera.MV_FRAME_OUT frame = new();
            // int ret = _camera.MV_CC_GetImageBuffer_NET(ref frame, (uint)timeoutMs);
            // if (ret != 0) return null;
            // var data = new byte[frame.stFrameInfo.nFrameLen];
            // Marshal.Copy(frame.pBufAddr, data, 0, data.Length);
            // _camera.MV_CC_FreeImageBuffer_NET(ref frame);
            // return new FrameData { Width = frame.stFrameInfo.nWidth, … };

            Debug.WriteLine("[相机] MVS GrabOne 骨架,需补全官方 SDK 调用");
            return null;
        }

        public void SoftwareTriggerOnce()
        {
            EnsureOpen();
            // _camera.MV_CC_SetCommandValue_NET("TriggerSoftware");
        }

        private void EnsureOpen()
        {
            if (!_opened) throw new CameraException("相机尚未打开,请先调用 Open()");
        }

        public void Dispose() => Close();
    }
    ICamera

    namespace IndustrialCameraDemo;

    /// <summary>
    /// 采集到的一帧图像(与具体品牌无关的统一结构)
    /// </summary>
    public sealed class FrameData : IDisposable
    {
        public int Width { get; init; }
        public int Height { get; init; }
        public byte[] PixelData { get; init; } = Array.Empty<byte>();

        /// <summary>像素格式名(Mono8 / BayerRG8 / RGB8 等)</summary>
        public string PixelFormat { get; init; } = "Mono8";

        public ulong FrameNumber { get; init; }
        public long TimestampNs { get; init; }

        /// <summary>底层 SDK 的原始帧对象(需要高级操作时可取用)</summary>
        public object? Native { get; init; }

        public void Dispose() => (Native as IDisposable)?.Dispose();
    }

    /// <summary>
    /// 工业相机统一抽象:枚举 → 打开 → 配置 → 取流 → 关闭
    /// 换品牌只需换一个实现,业务代码不动
    /// </summary>
    public interface ICamera : IDisposable
    {
        string Name { get; }

        void Open();
        void Close();
        bool IsOpen { get; }

        // —- 参数配置(GenICam 标准命名)—-
        void SetExposureUs(double microseconds);
        void SetGain(double gain);
        void SetRoi(int offsetX, int offsetY, int width, int height);
        void SetContinuousMode();                 // 连续采集(内触发)
        void SetSoftwareTriggerMode();            // 软触发
        void SetHardwareTriggerMode();            // 硬触发(外触发/光电开关)

        // —- 取流 —-
        void StartGrab();
        void StopGrab();
        FrameData? GrabOne(int timeoutMs = 5000); // 主动取一帧
        void SoftwareTriggerOnce();               // 发一次软触发
    }

    /// <summary>相机参数异常</summary>
    public class CameraException : Exception
    {
        public CameraException(string message, Exception? inner = null) : base(message, inner) { }
    }

    ImageSaver

    namespace IndustrialCameraDemo;

    /// <summary>
    /// 把采集到的帧保存为 PGM(灰度)/ PPM(彩色)文件。
    /// 纯托管实现,不依赖 System.Drawing / OpenCV,跨平台可直接用。
    /// 若需 JPEG/PNG,可换成 ImageSharp 或 OpenCvSharp 再编码。
    /// </summary>
    public static class ImageSaver
    {
        /// <summary>保存单通道灰度图(Mono8)</summary>
        public static void SaveMono8(string path, byte[] data, int width, int height)
        {
            using var fs = File.Create(path);
            using var w = new StreamWriter(fs);
            w.Write($"P5\\n{width} {height}\\n255\\n");
            w.Flush();
            fs.Write(data, 0, Math.Min(data.Length, width * height));
        }

        /// <summary>保存三通道彩色图(RGB8)</summary>
        public static void SaveRgb8(string path, byte[] data, int width, int height)
        {
            using var fs = File.Create(path);
            using var w = new StreamWriter(fs);
            w.Write($"P6\\n{width} {height}\\n255\\n");
            w.Flush();
            fs.Write(data, 0, Math.Min(data.Length, width * height * 3));
        }

        /// <summary>根据帧的像素格式自动选择保存方式</summary>
        public static void SaveAuto(FrameData frame, string path)
        {
            if (frame.PixelFormat.Contains("RGB", StringComparison.OrdinalIgnoreCase))
                SaveRgb8(path, frame.PixelData, frame.Width, frame.Height);
            else
                SaveMono8(path, frame.PixelData, frame.Width, frame.Height); // Bayer 也可当灰度粗看
        }
    }

    Program.cs

    using System.Diagnostics;
    using IndustrialCameraDemo;

    // ==========================================================
    // C# 工业相机采集模板(Basler pylon / 海康 MVS 通用接口)
    // 流程:枚举 → 打开 → 配参 → 连续采 N 帧 → 软触发 → 释放
    // ==========================================================

    var outDir = Path.Combine(Directory.GetCurrentDirectory(), "frames");
    Directory.CreateDirectory(outDir);

    Console.WriteLine("=== 工业相机采集模板 ===\\n");

    // ———- 1. 枚举相机 ———-
    var devices = BaslerPylonCamera.Enumerate();
    if (devices.Count == 0)
    {
        Console.WriteLine("未发现 Basler 相机。请检查:网线/USB 连接、pylon Runtime、IP 配置(GigE 需同网段)。");
        Console.WriteLine("提示:若为海康相机,请改用 HikvisionMvsCamera 并补全官方 SDK 调用。");
        return;
    }

    for (int i = 0; i < devices.Count; i++)
        Console.WriteLine($"[{i}] {devices[i]}");

    Console.Write("\\n选择相机编号(默认 0):");
    var input = Console.ReadLine();
    int index = int.TryParse(input, out var v) ? v : 0;
    if (index < 0 || index >= devices.Count) index = 0;

    // ———- 2. 打开相机 ———-
    using ICamera camera = new BaslerPylonCamera();   // 换品牌:new HikvisionMvsCamera()
    camera.Open();
    Console.WriteLine($"已打开:{camera.Name}\\n");

    try
    {
        // ———- 3. 配置参数 ———-
        camera.SetExposureUs(10_000);   // 曝光 10ms(弱光加大,运动物体减小)
        camera.SetGain(0);              // 增益 0(先靠曝光,增益会放大噪声)
        // camera.SetRoi(0, 0, 1920, 1200); // 需要提速时裁剪 ROI

        // ———- 4. 模式 A:连续采集,保存 10 帧 ———-
        Console.WriteLine("— 模式 A:连续采集 —");
        camera.SetContinuousMode();
        camera.StartGrab();

        for (int i = 0; i < 10; i++)
        {
            var frame = camera.GrabOne(5000);
            if (frame is null)
            {
                Console.WriteLine($"第 {i} 帧采集失败(超时或损坏)");
                continue;
            }

            var path = Path.Combine(outDir, $"continuous_{i:D3}.pgm");
            ImageSaver.SaveAuto(frame, path);
            Console.WriteLine($"帧 {frame.FrameNumber}:{frame.Width}x{frame.Height} " +
                              $"{frame.PixelFormat} → {Path.GetFileName(path)}");
        }

        camera.StopGrab();

        // ———- 5. 模式 B:软触发(外部信号到来才拍) ———-
        Console.WriteLine("\\n— 模式 B:软触发 —");
        camera.SetSoftwareTriggerMode();
        camera.StartGrab();

        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine($"触发第 {i + 1} 次…");
            camera.SoftwareTriggerOnce();          // 相当于外部传感器给了一个信号

            var frame = camera.GrabOne(5000);
            if (frame is null)
            {
                Console.WriteLine("  触发后未取到帧");
                continue;
            }

            var path = Path.Combine(outDir, $"trigger_{i:D3}.pgm");
            ImageSaver.SaveAuto(frame, path);
            Console.WriteLine($"  已保存:{Path.GetFileName(path)}(时间戳 {frame.TimestampNs} ns)");
        }

        camera.StopGrab();

        // ———- 6. 模式 C:硬触发(产线实际用法,按需启用) ———-
        // camera.SetHardwareTriggerMode();
        // camera.StartGrab();
        // while (!Console.KeyAvailable)
        // {
        //     var f = camera.GrabOne(1000);
        //     if (f != null) { /* 处理:测量 / 定位 / 检测 */ }
        // }
        // camera.StopGrab();

        Console.WriteLine($"\\n图片已保存至:{outDir}");
    }
    catch (CameraException ex)
    {
        Console.WriteLine($"相机错误:{ex.Message}");
    }
    finally
    {
        // ———- 7. 释放 ———-
        camera.Close();
        Console.WriteLine("相机已关闭,资源已释放。");
    }
     

    # C# 工业相机采集模板(Basler pylon / 海康 MVS)

    基于 **.NET 8** 的工业相机采集脚手架,用**统一接口**屏蔽品牌差异,

    业务代码只需面向 `ICamera` 编程,换相机只换实现类。

    ## 项目结构

    ```

    IndustrialCameraDemo/

    ├── ICamera.cs               # 统一抽象接口 + FrameData 帧结构

    ├── BaslerPylonCamera.cs     # Basler 相机实现(pylon / GenICam)✅ 可直接用

    ├── HikvisionMvsCamera.cs    # 海康/华睿 MVS 实现(骨架,需补全官方 SDK)

    ├── ImageSaver.cs            # PGM/PPM 保存(零第三方依赖)

    ├── Program.cs               # 完整流程示例:枚举→配参→连续→软触发→释放

    └── IndustrialCameraDemo.csproj

    ```

    ## 一、环境准备

    ### Basler 相机(GigE / USB3)

    1. 安装 **Basler pylon Runtime**(官网免费下载)

    2. GigE 相机:用 pylon IP Configurator 给相机设 IP,**与主机同网段**

    3. 用 pylon Viewer 先确认能出图(排错第一步,别急着写代码)

    4. 还原 NuGet 包:

       ```bash

       dotnet restore

       ```

    ### 海康 / 华睿 MVS 相机

    1. 安装 **MVS SDK**

    2. 在 `IndustrialCameraDemo.csproj` 中取消注释 `MvCameraControl.Net.dll` 的引用,并改成实际路径

    3. 按官方 SDK 补全 `HikvisionMvsCamera.cs` 中被注释的调用

    ## 二、运行

    ```bash

    cd IndustrialCameraDemo

    dotnet run

    ```

    程序会:

    1. 枚举相机 → 让你选编号

    2. 设曝光 10ms、增益 0

    3. **连续采集 10 帧** → 保存到 `frames/continuous_*.pgm`

    4. **软触发 3 次** → 保存到 `frames/trigger_*.pgm`

    5. 关闭相机释放资源

    ## 三、核心代码(记住这一套流程)

    ```csharp

    using ICamera camera = new BaslerPylonCamera();

    camera.Open();

    camera.SetExposureUs(10_000);     // 曝光

    camera.SetGain(0);                // 增益

    camera.SetContinuousMode();       // 或 SetSoftwareTriggerMode / SetHardwareTriggerMode

    camera.StartGrab();

    var frame = camera.GrabOne(5000); // 取一帧(超时 5s)

    if (frame != null) { /* frame.Width / Height / PixelData */ }

    camera.StopGrab();

    camera.Close();

    ```

    **软触发**(外部传感器 → 拍一张):

    ```csharp

    camera.SetSoftwareTriggerMode();

    camera.StartGrab();

    camera.SoftwareTriggerOnce();      // 相当于给了一个触发信号

    var f = camera.GrabOne(5000);

    ```

    ## 四、关键参数怎么调

    | 场景 | 曝光 | 增益 | 说明 |

    |—|—|—|—|

    | 静态、光足 | 5~20 ms | 0 | 优先拉曝光不加增益 |

    | 运动物体(飞拍) | 尽量短(<1 ms) | 适度提高 | 曝光长会拖影,靠光源补亮 |

    | 弱光 | 加长曝光 | 最后才加增益 | 增益会放大噪声 |

    | 提速 | 裁剪 ROI / Binning | — | 减少像素 = 提高帧率 |

    ## 五、常见坑(排查顺序)

    1. **枚举不到相机**

       – GigE:IP 是否同网段?防火墙?用 pylon Viewer 先试

       – USB3:是否插在 USB3 口(蓝色)?线缆是否达标?

    2. **能连上但不出图 / 超时**

       – 触发模式是否设成 On 却没给触发信号?(先切连续模式验证)

       – 曝光是否过长导致帧率极低?

    3. **图像全黑**

       – 镜头盖没摘 / 光圈没开 / 光源没触发

       – 曝光或增益太小

    4. **运动拖影**

       – 卷帘快门 → 换**全局快门**相机;或缩短曝光 + 加频闪光源

    5. **丢帧**

       – 提高 `GrabOne` 超时、用回调取流、检查网卡巨帧(GigE 建议开 Jumbo Frame)

       – 处理耗时 > 采集间隔时,把处理放到独立线程/队列

    ## 六、下一步扩展

    – **实时预览**:用 OpenCvSharp / ImageSharp 把 `PixelData` 转成 Mat/Bitmap 显示

    – **回调取流**:高帧率场景改用 `StreamGrabber.ImageGrabbed` 事件(本模板用主动取流,简单可靠)

    – **接算法**:在 `GrabOne` 后接 Halcon / OpenCV 做测量、定位、缺陷检测

    – **接 PLC**:用 `System.Net.Sockets` 或 Modbus 库把结果写给产线

    – **落数据库**:结果 + 图片路径存 SQLite / SQL Server

    > ⚠️ 模板中 `HikvisionMvsCamera.cs` 为骨架代码,需按官方 MVS SDK 补全;

    > `Basler.Pylon` 包版本请以实际可用版本为准(csproj 中可调整)。

    如果您觉得我的文章对您有帮助的话,烦请您打赏一元,我买瓶水喝;您的支持将是我继续分享的无限动力,谢谢!

    赞(0)
    未经允许不得转载:171主机测评 » C# + 工业相机 SDK 的采集代码模板
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址