直接 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 外触发),产线实际用法 |
三个设计取舍
运行前必做
排坑速查(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 中可调整)。
如果您觉得我的文章对您有帮助的话,烦请您打赏一元,我买瓶水喝;您的支持将是我继续分享的无限动力,谢谢!




