简介:这份资源是大恒CG300采集卡的C#示例工程,面向从事工业控制、图像处理与科学数据采集的开发者,尤其是需要基于该卡进行二次开发、调试多通道采集与实时显示的技术人员。压缩包共28个文件,约78KB,以cs源码、exe可执行程序、resources资源、pdb调试符号、resx与settings配置等为主,包含解决方案与项目文件,结构完整可直接编译运行。目前已有492人学习下载。Demo覆盖采集卡初始化、四路信号采集、场采集帧显示等核心流程,并针对描述中提到的晶振频率问题给出排错思路——将晶振改为28MHz后即可正常工作,帮助读者理解硬件时钟与驱动配置的匹配关系。通过研读源码与界面逻辑,可掌握多线程采集、数据可视化与设备参数调整方法,为构建稳定高效的采集系统提供可复用的参考。1. 大恒CG300采集卡C#Demo:从开箱到出图的完整落地路径工业视觉项目里,采集卡到上位机这一段往往是最容易被低估的环节。相机选型、镜头、光源都调好了,结果卡在采集卡SDK调用上,图像出不来或者帧率上不去,这种翻车现场我见过太多次。大恒CG300这块卡在工业检测、医疗影像、科研成像里出镜率不低,它配套的C# Demo就是帮你把「卡能出图」这件事跑通的最小可执行单元。这份资源适合两类人:一是刚拿到CG300、手上只有一根线一张卡、不知道从哪行代码开始写的视觉工程师;二是已经在用但采集回调里丢帧、内存涨、颜色不对,想找个干净参考实现对照排查的老手。Demo本身不复杂,但它把SDK初始化、设备打开、通道配置、回调注册、图像格式转换这条链路完整串了一遍,照着走能省掉大量翻手册的时间。2. 拆开Demo看结构:CG300的C#封装到底封装了什么2.1 从SDK动态库到C#调用的映射关系大恒CG300的底层是一套C接口的动态库,C# Demo的核心工作就是用P/Invoke把这套C接口包成托管代码能调用的形式。你打开Demo的解决方案,通常会看到几个关键文件:一个是DahuaCapture.cs或类似命名的封装类,里面用[DllImport]声明了CG300_Init、CG300_OpenDevice、CG300_StartCapture这类函数;一个是主窗体,负责按钮事件和图像显示;还有一个配置文件或枚举定义,管通道号、分辨率、像素格式这些参数。常见做法是先把SDK安装目录下的.dll和.lib路径配好,C#项目里把平台目标设成x64——CG300的驱动和SDK基本都是64位的,如果你编译成AnyCPU或者x86,运行时会直接报BadImageFormatException,这个坑后面还会细说。封装层里最值得看的是回调函数的签名,C接口的回调通常是void (*)(int channel, void* data, int width, int height, int format)这种形式,C#这边要用Marshal.GetDelegateForFunctionPointer或者直接声明一个匹配的委托来接收。Demo里一般会把这个委托定义成静态字段,防止被GC回收导致回调失效——这是C#调C回调最经典的玄学问题,回调注册完跑几帧就崩,十有八九是委托被回收了。2.2 初始化、打开设备、配置通道的三段式流程CG300的调用顺序有硬性要求,不能跳步。Demo里的按钮事件基本是按这个顺序排的:// 第一步:初始化SDK,加载驱动 int ret = CG300_Init(); if (ret != 0) { MessageBox.Show($"SDK初始化失败,错误码:{ret}"); return; } // 第二步:打开指定设备,deviceIndex从0开始 ret = CG300_OpenDevice(0); if (ret != 0) { MessageBox.Show($"打开设备失败,错误码:{ret}"); CG300_Uninit(); return; } // 第三步:配置采集通道参数 // channel: 通道号,CG300一般支持多通道 // width/height: 采集分辨率,必须和信号源匹配 // format: 像素格式,常见有MONO8、RGB24、YUY2 ret = CG300_SetChannelParam(0, 1920, 1080, PIXEL_FORMAT_RGB24); if (ret != 0) { MessageBox.Show($"通道配置失败,错误码:{ret}"); CG300_CloseDevice(0); CG300_Uninit(); return; }这段代码的逻辑说明:CG300_Init负责加载底层驱动和分配全局资源,没调它后面所有函数都会返回未初始化错误。CG300_OpenDevice的参数是设备索引,单卡场景填0就行,多卡才需要遍历。CG300_SetChannelParam里的分辨率必须和实际输入信号一致,比如你接的是1080P的相机,这里填1280x720,采集出来的图像会花屏或者直接报参数错误。像素格式决定了后面回调里data指针指向的数据排列方式,RGB24是每像素3字节,MONO8是每像素1字节,选错了图像颜色会完全不对。参数怎么改:分辨率改width和height,但要注意CG300有最大采集带宽限制,高分辨率下帧率会下降。像素格式如果做灰度检测就选MONO8,省带宽也省后续转换;要做颜色识别就选RGB24,但数据量翻三倍。通道号在多路输入时才有意义,单路填0。2.3 回调注册与图像数据搬运配置完通道,下一步是注册回调函数并启动采集。Demo里通常这么写:// 定义回调委托,保持静态引用防止GC回收 private static CaptureCallback _callback = OnFrameReceived; // 注册回调 ret = CG300_RegisterCallback(0, _callback, IntPtr.Zero); // 启动采集 ret = CG300_StartCapture(0); // 回调函数实现 private static void OnFrameReceived(int channel, IntPtr data, int width, int height, int format) { // 计算数据长度 int stride = width * 3; // RGB24每像素3字节 int dataSize = stride * height; // 从非托管内存拷贝到托管数组 byte[] buffer = new byte[dataSize]; Marshal.Copy(data, buffer, 0, dataSize); // 转成Bitmap显示(这里用最简单的构造方式) Bitmap bmp = new Bitmap(width, height, stride, PixelFormat.Format24bppRgb, data); // 跨线程更新UI需要用Invoke form.BeginInvoke(new Action(() => { form.pictureBox1.Image?.Dispose(); form.pictureBox1.Image = bmp; })); }逻辑说明:CG300_RegisterCallback把C#的委托传给底层,底层每采集到一帧就调一次。回调运行在SDK的内部线程上,不是UI线程,所以直接操作控件会抛跨线程异常,必须用BeginInvoke或Invoke切回去。Marshal.Copy把非托管内存的数据拷到托管数组,这一步是必须的,因为回调返回后data指针指向的内存可能被SDK复用。如果直接在回调里用new Bitmap(..., data)而不拷贝,显示出来的图像可能撕裂或者花屏,这是新手最容易踩的坑之一。参数注意:stride不总是等于width * 3,有些SDK会做4字节对齐,实际行宽是((width * 3 + 3) / 4) * 4。Demo里如果没处理对齐,图像会出现斜条纹。稳妥做法是查SDK文档里的GetStride接口,或者直接用CG300_GetImageInfo拿实际行宽。3. 把Demo跑起来:环境配置与第一个可运行版本3.1 驱动安装与SDK路径配置拿到CG300和这份Demo,第一步不是开Visual Studio,而是装驱动。大恒的采集卡驱动一般随卡附带光盘或者从官网下载,安装完设备管理器里能看到采集卡设备,没有黄色感叹号才算正常。驱动装完再装SDK,SDK会往系统目录写动态库,同时提供头文件和lib文件。C#项目里要确保能找得到这些动态库。常见做法有两种:一是把SDK的bin目录加到系统PATH环境变量;二是把需要的.dll直接拷到C#的bin\Debug或bin\Release目录下。我一般用第二种,因为部署的时候不用再配环境,拷过去就能跑。需要拷的通常包括CG300.dll、CG300Core.dll以及可能的依赖库如libusb或pthread相关的东西,具体看SDK的redist目录。项目属性里把平台目标设成x64,这个前面提过。另外如果Demo用了不安全代码(unsafe关键字),要在项目属性里勾上「允许不安全代码」。有些封装用Marshal.Copy就不用开unsafe,但如果你看到代码里有byte*指针操作,那就必须开。3.2 编译报错的常见原因与修复第一次编译Demo,大概率会遇到这几类错误:第一类是DllNotFoundException,提示找不到CG300.dll。原因就是动态库不在可执行文件的搜索路径里。解决方法是把SDK的bin目录下所有dll拷到输出目录,或者用SetDllDirectory在代码里动态指定路径。第二类是EntryPointNotFoundException,提示找不到某个函数入口。这通常是因为SDK版本和Demo不匹配,函数名或者调用约定变了。C接口默认是cdecl,但有些SDK用stdcall,C#的[DllImport]里要对应写CallingConvention = CallingConvention.Cdecl或StdCall。写错了就会在运行时找不到入口。第三类是BadImageFormatException,前面说过,平台目标不对。把x86改成x64,重新编译。第四类是回调注册返回错误码,但设备明明打开了。检查回调委托是不是被声明成了局部变量,局部变量在方法结束后就可能被GC回收,底层再调就是野指针。改成静态字段或者用GCHandle.Alloc钉住。3.3 采集参数调优:分辨率、帧率与像素格式的取舍Demo跑通之后,下一步是根据实际场景调参数。CG300的采集能力有上限,不是所有参数组合都能满帧跑。下面这张表是我在实际项目里总结的常见组合和注意事项:分辨率像素格式理论帧率实际注意事项1920x1080RGB2460fps带宽占用高,建议用PCIe x4以上插槽1920x1080MONO8120fps灰度场景首选,CPU占用低1280x1024RGB2490fps注意行对齐,stride可能不是3840640x480RGB24240fps高帧率场景,回调里别做耗时操作3840x2160MONO830fps4K场景,内存拷贝耗时明显调参的时候有个原则:回调函数里只做数据拷贝和入队,不做图像处理。我见过有人在回调里直接跑OpenCV的滤波,结果帧率从120掉到15,还以为是卡的问题。正确做法是回调里把数据拷到一个环形缓冲区,另开一个线程从缓冲区取数据做处理。Demo为了简洁可能直接在回调里显示,实际项目一定要解耦。像素格式的选择还影响后续处理。MONO8出来就是单通道灰度,OpenCV里直接Mat构造就行。RGB24要注意BGR和RGB的顺序,大恒SDK出来的RGB24通常是R在前,但OpenCV的Mat默认是BGR,直接塞进去颜色会偏蓝。要么用cvtColor转一下,要么构造Mat的时候指定通道顺序。4. 避坑与排查:CG300 C#开发中那些血泪经验4.1 回调丢帧与内存泄漏的排查现象:采集跑了几分钟,帧率从60掉到30,内存占用持续上涨,最后程序卡死。原因:回调里每帧都new Bitmap,但没有及时Dispose。GDI+对象不释放,内存越吃越多,GC来不及回收。另外如果回调处理速度跟不上采集速度,SDK的内部缓冲区会满,开始丢帧。解决:回调里只做Marshal.Copy到预分配的缓冲区,用双缓冲或环形缓冲。显示线程从缓冲区取最新帧,旧的直接覆盖。Bitmap对象用完立即Dispose,或者用using包起来。如果还是丢帧,检查回调里的操作是不是太重,把耗时逻辑挪到独立线程。4.2 图像花屏、斜条纹与颜色异常的根因现象:采集出来的图像有斜条纹,或者颜色整体偏蓝/偏红。原因:斜条纹九成是stride没对齐。SDK返回的行宽可能是4字节对齐的,你按width * 3算,每行差几个字节,累积起来就是斜的。颜色异常是像素格式的通道顺序问题,RGB24和BGR24搞混了。解决:用SDK提供的GetStride接口拿实际行宽,或者自己算stride = ((width * bytesPerPixel + 3) / 4) * 4。颜色问题在构造Bitmap或Mat的时候指定正确的PixelFormat,OpenCV里用COLOR_RGB2BGR转一下。4.3 多通道采集时的资源竞争现象:CG300支持多通道,但同时开两个通道采集,程序随机崩溃或者某个通道没图像。原因:多个回调同时触发,如果回调里访问了共享资源(比如同一个缓冲区、同一个Bitmap对象)没有加锁,就会竞争。另外SDK本身可能对多通道的调用线程有要求,某些函数不是线程安全的。解决:每个通道独立缓冲区和独立显示控件,回调之间不共享可变状态。如果必须共享,用lock或ConcurrentQueue。SDK的初始化、打开设备这些操作在主线程做完再启动采集,不要在回调里调SDK的控制函数。4.4 程序退出时的清理顺序现象:关闭程序时偶尔崩溃,或者下次打开设备失败,提示设备被占用。原因:退出时没有按顺序停止采集、注销回调、关闭设备、反初始化。如果直接关窗体,SDK的资源没释放,设备句柄泄漏,下次就打开不了。解决:在窗体的FormClosing事件里按这个顺序清理:先CG300_StopCapture,再CG300_UnregisterCallback,然后CG300_CloseDevice,最后CG300_Uninit。每一步都检查返回值,出错也继续往下走。清理完再允许窗体关闭。4.5 不同Windows版本与.NET运行时的兼容性现象:在开发机上跑得好好的Demo,拷到现场工控机上就报错,要么找不到dll,要么回调不触发。原因:工控机可能是Windows 7或者精简版系统,缺少某些运行时库。另外.NET Framework版本不一致也会导致行为差异。解决:确认目标机器装了对应版本的.NET Framework(Demo如果是.NET Framework 4.5就装4.5以上)。VC++运行库也要装,因为SDK的dll可能依赖msvcr120.dll之类的。如果还是不行,用Dependency Walker查一下dll的依赖树,缺什么补什么。现场部署我一般会把所有依赖打包成一个文件夹,用批处理设置PATH后启动,减少环境差异。5. 进阶用法:从Demo到可交付的采集模块5.1 把回调数据接入OpenCVSharp做实时处理Demo只负责出图,实际项目要在采集的同时做检测。我一般会把回调里的数据直接喂给OpenCVSharp的Mat,省掉Bitmap这一层。关键代码如下:// 预分配Mat,避免每帧new private Mat _mat = new Mat(1080, 1920, MatType.CV_8UC3); private void OnFrameReceived(int channel, IntPtr data, int width, int height, int format) { // 直接把非托管数据拷进Mat的Data指针 int stride = width * 3; int totalBytes = stride * height; // Mat的Step可能和stride不一致,用Mat的Step逐行拷 unsafe { byte* src = (byte*)data; byte* dst = (byte*)_mat.DataPointer; for (int y = 0; y < height; y++) { Buffer.MemoryCopy(src + y * stride, dst + y * _mat.Step(), stride, stride); } } // 此时_mat就是最新帧,可以送检测线程 _processQueue.Enqueue(_mat.Clone()); }逻辑说明:Mat.DataPointer拿到Mat内部缓冲区的指针,逐行拷贝是因为Mat的Step可能大于width * 3(OpenCV也会做对齐)。Clone()是必须的,因为_mat会被下一帧覆盖,不克隆的话处理线程拿到的数据会变。unsafe代码需要项目开启允许不安全代码。参数注意:Mat的构造参数是(rows, cols, type),对应(height, width, CV_8UC3)。如果是MONO8就改成CV_8UC1,拷贝时stride等于width。检测线程从_processQueue取Mat,处理完记得Dispose,否则内存涨得比回调里new Bitmap还快。5.2 帧率统计与采集质量验证Demo跑通不代表采集质量达标,我习惯加一个帧率统计来验证。简单做法是在回调里记时间戳,每秒统计一次:private long _frameCount = 0; private Stopwatch _sw = Stopwatch.StartNew(); private void OnFrameReceived(...) { Interlocked.Increment(ref _frameCount); if (_sw.ElapsedMilliseconds >= 1000) { long fps = Interlocked.Exchange(ref _frameCount, 0); Console.WriteLine($"当前帧率:{fps}fps"); _sw.Restart(); } // ... 数据处理 }这个统计能帮你判断采集是否稳定。如果帧率波动超过10%,检查是不是回调处理太重或者PCIe带宽被其他设备抢了。稳定在标称帧率的95%以上才算合格。5.3 一个我踩过的坑:回调里别调SDK控制函数最后说一个我亲身翻车的经历。早期做项目的时候,我在回调里根据图像亮度动态调曝光,调的是SDK的CG300_SetExposure。结果程序跑几分钟就死锁,调试发现SDK的控制函数和回调在同一个线程上下文里,回调里再调控制函数就重入了,底层锁没释放。从那以后我每次做采集模块,都强制把控制指令和回调处理分成两个线程,用队列通信。回调只负责搬数据,控制线程负责调参数,中间用BlockingCollection传递指令。这个习惯帮我省掉了至少三次现场排查的时间。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源是大恒CG300采集卡的C#示例工程,面向从事工业控制、图像处理与科学数据采集的开发者,尤其是需要基于该卡进行二次开发、调试多通道采集与实时显示的技术人员。压缩包共28个文件,约78KB,以cs源码、exe可执行程序、resources资源、pdb调试符号、resx与settings配置等为主,包含解决方案与项目文件,结构完整可直接编译运行。目前已有492人学习下载。Demo覆盖采集卡初始化、四路信号采集、场采集帧显示等核心流程,并针对描述中提到的晶振频率问题给出排错思路——将晶振改为28MHz后即可正常工作,帮助读者理解硬件时钟与驱动配置的匹配关系。通过研读源码与界面逻辑,可掌握多线程采集、数据可视化与设备参数调整方法,为构建稳定高效的采集系统提供可复用的参考。1. 大恒CG300采集卡C#Demo:从开箱到出图的完整落地路径工业视觉项目里,采集卡到上位机这一段往往是最容易被低估的环节。相机选型、镜头、光源都调好了,结果卡在采集卡SDK调用上,图像出不来或者帧率上不去,这种翻车现场我见过太多次。大恒CG300这块卡在工业检测、医疗影像、科研成像里出镜率不低,它配套的C# Demo就是帮你把「卡能出图」这件事跑通的最小可执行单元。这份资源适合两类人:一是刚拿到CG300、手上只有一根线一张卡、不知道从哪行代码开始写的视觉工程师;二是已经在用但采集回调里丢帧、内存涨、颜色不对,想找个干净参考实现对照排查的老手。Demo本身不复杂,但它把SDK初始化、设备打开、通道配置、回调注册、图像格式转换这条链路完整串了一遍,照着走能省掉大量翻手册的时间。2. 拆开Demo看结构:CG300的C#封装到底封装了什么2.1 从SDK动态库到C#调用的映射关系大恒CG300的底层是一套C接口的动态库,C# Demo的核心工作就是用P/Invoke把这套C接口包成托管代码能调用的形式。你打开Demo的解决方案,通常会看到几个关键文件:一个是DahuaCapture.cs或类似命名的封装类,里面用[DllImport]声明了CG300_Init、CG300_OpenDevice、CG300_StartCapture这类函数;一个是主窗体,负责按钮事件和图像显示;还有一个配置文件或枚举定义,管通道号、分辨率、像素格式这些参数。常见做法是先把SDK安装目录下的.dll和.lib路径配好,C#项目里把平台目标设成x64——CG300的驱动和SDK基本都是64位的,如果你编译成AnyCPU或者x86,运行时会直接报BadImageFormatException,这个坑后面还会细说。封装层里最值得看的是回调函数的签名,C接口的回调通常是void (*)(int channel, void* data, int width, int height, int format)这种形式,C#这边要用Marshal.GetDelegateForFunctionPointer或者直接声明一个匹配的委托来接收。Demo里一般会把这个委托定义成静态字段,防止被GC回收导致回调失效——这是C#调C回调最经典的玄学问题,回调注册完跑几帧就崩,十有八九是委托被回收了。2.2 初始化、打开设备、配置通道的三段式流程CG300的调用顺序有硬性要求,不能跳步。Demo里的按钮事件基本是按这个顺序排的:// 第一步:初始化SDK,加载驱动 int ret = CG300_Init(); if (ret != 0) { MessageBox.Show($"SDK初始化失败,错误码:{ret}"); return; } // 第二步:打开指定设备,deviceIndex从0开始 ret = CG300_OpenDevice(0); if (ret != 0) { MessageBox.Show($"打开设备失败,错误码:{ret}"); CG300_Uninit(); return; } // 第三步:配置采集通道参数 // channel: 通道号,CG300一般支持多通道 // width/height: 采集分辨率,必须和信号源匹配 // format: 像素格式,常见有MONO8、RGB24、YUY2 ret = CG300_SetChannelParam(0, 1920, 1080, PIXEL_FORMAT_RGB24); if (ret != 0) { MessageBox.Show($"通道配置失败,错误码:{ret}"); CG300_CloseDevice(0); CG300_Uninit(); return; }这段代码的逻辑说明:CG300_Init负责加载底层驱动和分配全局资源,没调它后面所有函数都会返回未初始化错误。CG300_OpenDevice的参数是设备索引,单卡场景填0就行,多卡才需要遍历。CG300_SetChannelParam里的分辨率必须和实际输入信号一致,比如你接的是1080P的相机,这里填1280x720,采集出来的图像会花屏或者直接报参数错误。像素格式决定了后面回调里data指针指向的数据排列方式,RGB24是每像素3字节,MONO8是每像素1字节,选错了图像颜色会完全不对。参数怎么改:分辨率改width和height,但要注意CG300有最大采集带宽限制,高分辨率下帧率会下降。像素格式如果做灰度检测就选MONO8,省带宽也省后续转换;要做颜色识别就选RGB24,但数据量翻三倍。通道号在多路输入时才有意义,单路填0。2.3 回调注册与图像数据搬运配置完通道,下一步是注册回调函数并启动采集。Demo里通常这么写:// 定义回调委托,保持静态引用防止GC回收 private static CaptureCallback _callback = OnFrameReceived; // 注册回调 ret = CG300_RegisterCallback(0, _callback, IntPtr.Zero); // 启动采集 ret = CG300_StartCapture(0); // 回调函数实现 private static void OnFrameReceived(int channel, IntPtr data, int width, int height, int format) { // 计算数据长度 int stride = width * 3; // RGB24每像素3字节 int dataSize = stride * height; // 从非托管内存拷贝到托管数组 byte[] buffer = new byte[dataSize]; Marshal.Copy(data, buffer, 0, dataSize); // 转成Bitmap显示(这里用最简单的构造方式) Bitmap bmp = new Bitmap(width, height, stride, PixelFormat.Format24bppRgb, data); // 跨线程更新UI需要用Invoke form.BeginInvoke(new Action(() => { form.pictureBox1.Image?.Dispose(); form.pictureBox1.Image = bmp; })); }逻辑说明:CG300_RegisterCallback把C#的委托传给底层,底层每采集到一帧就调一次。回调运行在SDK的内部线程上,不是UI线程,所以直接操作控件会抛跨线程异常,必须用BeginInvoke或Invoke切回去。Marshal.Copy把非托管内存的数据拷到托管数组,这一步是必须的,因为回调返回后data指针指向的内存可能被SDK复用。如果直接在回调里用new Bitmap(..., data)而不拷贝,显示出来的图像可能撕裂或者花屏,这是新手最容易踩的坑之一。参数注意:stride不总是等于width * 3,有些SDK会做4字节对齐,实际行宽是((width * 3 + 3) / 4) * 4。Demo里如果没处理对齐,图像会出现斜条纹。稳妥做法是查SDK文档里的GetStride接口,或者直接用CG300_GetImageInfo拿实际行宽。3. 把Demo跑起来:环境配置与第一个可运行版本3.1 驱动安装与SDK路径配置拿到CG300和这份Demo,第一步不是开Visual Studio,而是装驱动。大恒的采集卡驱动一般随卡附带光盘或者从官网下载,安装完设备管理器里能看到采集卡设备,没有黄色感叹号才算正常。驱动装完再装SDK,SDK会往系统目录写动态库,同时提供头文件和lib文件。C#项目里要确保能找得到这些动态库。常见做法有两种:一是把SDK的bin目录加到系统PATH环境变量;二是把需要的.dll直接拷到C#的bin\Debug或bin\Release目录下。我一般用第二种,因为部署的时候不用再配环境,拷过去就能跑。需要拷的通常包括CG300.dll、CG300Core.dll以及可能的依赖库如libusb或pthread相关的东西,具体看SDK的redist目录。项目属性里把平台目标设成x64,这个前面提过。另外如果Demo用了不安全代码(unsafe关键字),要在项目属性里勾上「允许不安全代码」。有些封装用Marshal.Copy就不用开unsafe,但如果你看到代码里有byte*指针操作,那就必须开。3.2 编译报错的常见原因与修复第一次编译Demo,大概率会遇到这几类错误:第一类是DllNotFoundException,提示找不到CG300.dll。原因就是动态库不在可执行文件的搜索路径里。解决方法是把SDK的bin目录下所有dll拷到输出目录,或者用SetDllDirectory在代码里动态指定路径。第二类是EntryPointNotFoundException,提示找不到某个函数入口。这通常是因为SDK版本和Demo不匹配,函数名或者调用约定变了。C接口默认是cdecl,但有些SDK用stdcall,C#的[DllImport]里要对应写CallingConvention = CallingConvention.Cdecl或StdCall。写错了就会在运行时找不到入口。第三类是BadImageFormatException,前面说过,平台目标不对。把x86改成x64,重新编译。第四类是回调注册返回错误码,但设备明明打开了。检查回调委托是不是被声明成了局部变量,局部变量在方法结束后就可能被GC回收,底层再调就是野指针。改成静态字段或者用GCHandle.Alloc钉住。3.3 采集参数调优:分辨率、帧率与像素格式的取舍Demo跑通之后,下一步是根据实际场景调参数。CG300的采集能力有上限,不是所有参数组合都能满帧跑。下面这张表是我在实际项目里总结的常见组合和注意事项:分辨率像素格式理论帧率实际注意事项1920x1080RGB2460fps带宽占用高,建议用PCIe x4以上插槽1920x1080MONO8120fps灰度场景首选,CPU占用低1280x1024RGB2490fps注意行对齐,stride可能不是3840640x480RGB24240fps高帧率场景,回调里别做耗时操作3840x2160MONO830fps4K场景,内存拷贝耗时明显调参的时候有个原则:回调函数里只做数据拷贝和入队,不做图像处理。我见过有人在回调里直接跑OpenCV的滤波,结果帧率从120掉到15,还以为是卡的问题。正确做法是回调里把数据拷到一个环形缓冲区,另开一个线程从缓冲区取数据做处理。Demo为了简洁可能直接在回调里显示,实际项目一定要解耦。像素格式的选择还影响后续处理。MONO8出来就是单通道灰度,OpenCV里直接Mat构造就行。RGB24要注意BGR和RGB的顺序,大恒SDK出来的RGB24通常是R在前,但OpenCV的Mat默认是BGR,直接塞进去颜色会偏蓝。要么用cvtColor转一下,要么构造Mat的时候指定通道顺序。4. 避坑与排查:CG300 C#开发中那些血泪经验4.1 回调丢帧与内存泄漏的排查现象:采集跑了几分钟,帧率从60掉到30,内存占用持续上涨,最后程序卡死。原因:回调里每帧都new Bitmap,但没有及时Dispose。GDI+对象不释放,内存越吃越多,GC来不及回收。另外如果回调处理速度跟不上采集速度,SDK的内部缓冲区会满,开始丢帧。解决:回调里只做Marshal.Copy到预分配的缓冲区,用双缓冲或环形缓冲。显示线程从缓冲区取最新帧,旧的直接覆盖。Bitmap对象用完立即Dispose,或者用using包起来。如果还是丢帧,检查回调里的操作是不是太重,把耗时逻辑挪到独立线程。4.2 图像花屏、斜条纹与颜色异常的根因现象:采集出来的图像有斜条纹,或者颜色整体偏蓝/偏红。原因:斜条纹九成是stride没对齐。SDK返回的行宽可能是4字节对齐的,你按width * 3算,每行差几个字节,累积起来就是斜的。颜色异常是像素格式的通道顺序问题,RGB24和BGR24搞混了。解决:用SDK提供的GetStride接口拿实际行宽,或者自己算stride = ((width * bytesPerPixel + 3) / 4) * 4。颜色问题在构造Bitmap或Mat的时候指定正确的PixelFormat,OpenCV里用COLOR_RGB2BGR转一下。4.3 多通道采集时的资源竞争现象:CG300支持多通道,但同时开两个通道采集,程序随机崩溃或者某个通道没图像。原因:多个回调同时触发,如果回调里访问了共享资源(比如同一个缓冲区、同一个Bitmap对象)没有加锁,就会竞争。另外SDK本身可能对多通道的调用线程有要求,某些函数不是线程安全的。解决:每个通道独立缓冲区和独立显示控件,回调之间不共享可变状态。如果必须共享,用lock或ConcurrentQueue。SDK的初始化、打开设备这些操作在主线程做完再启动采集,不要在回调里调SDK的控制函数。4.4 程序退出时的清理顺序现象:关闭程序时偶尔崩溃,或者下次打开设备失败,提示设备被占用。原因:退出时没有按顺序停止采集、注销回调、关闭设备、反初始化。如果直接关窗体,SDK的资源没释放,设备句柄泄漏,下次就打开不了。解决:在窗体的FormClosing事件里按这个顺序清理:先CG300_StopCapture,再CG300_UnregisterCallback,然后CG300_CloseDevice,最后CG300_Uninit。每一步都检查返回值,出错也继续往下走。清理完再允许窗体关闭。4.5 不同Windows版本与.NET运行时的兼容性现象:在开发机上跑得好好的Demo,拷到现场工控机上就报错,要么找不到dll,要么回调不触发。原因:工控机可能是Windows 7或者精简版系统,缺少某些运行时库。另外.NET Framework版本不一致也会导致行为差异。解决:确认目标机器装了对应版本的.NET Framework(Demo如果是.NET Framework 4.5就装4.5以上)。VC++运行库也要装,因为SDK的dll可能依赖msvcr120.dll之类的。如果还是不行,用Dependency Walker查一下dll的依赖树,缺什么补什么。现场部署我一般会把所有依赖打包成一个文件夹,用批处理设置PATH后启动,减少环境差异。5. 进阶用法:从Demo到可交付的采集模块5.1 把回调数据接入OpenCVSharp做实时处理Demo只负责出图,实际项目要在采集的同时做检测。我一般会把回调里的数据直接喂给OpenCVSharp的Mat,省掉Bitmap这一层。关键代码如下:// 预分配Mat,避免每帧new private Mat _mat = new Mat(1080, 1920, MatType.CV_8UC3); private void OnFrameReceived(int channel, IntPtr data, int width, int height, int format) { // 直接把非托管数据拷进Mat的Data指针 int stride = width * 3; int totalBytes = stride * height; // Mat的Step可能和stride不一致,用Mat的Step逐行拷 unsafe { byte* src = (byte*)data; byte* dst = (byte*)_mat.DataPointer; for (int y = 0; y < height; y++) { Buffer.MemoryCopy(src + y * stride, dst + y * _mat.Step(), stride, stride); } } // 此时_mat就是最新帧,可以送检测线程 _processQueue.Enqueue(_mat.Clone()); }逻辑说明:Mat.DataPointer拿到Mat内部缓冲区的指针,逐行拷贝是因为Mat的Step可能大于width * 3(OpenCV也会做对齐)。Clone()是必须的,因为_mat会被下一帧覆盖,不克隆的话处理线程拿到的数据会变。unsafe代码需要项目开启允许不安全代码。参数注意:Mat的构造参数是(rows, cols, type),对应(height, width, CV_8UC3)。如果是MONO8就改成CV_8UC1,拷贝时stride等于width。检测线程从_processQueue取Mat,处理完记得Dispose,否则内存涨得比回调里new Bitmap还快。5.2 帧率统计与采集质量验证Demo跑通不代表采集质量达标,我习惯加一个帧率统计来验证。简单做法是在回调里记时间戳,每秒统计一次:private long _frameCount = 0; private Stopwatch _sw = Stopwatch.StartNew(); private void OnFrameReceived(...) { Interlocked.Increment(ref _frameCount); if (_sw.ElapsedMilliseconds >= 1000) { long fps = Interlocked.Exchange(ref _frameCount, 0); Console.WriteLine($"当前帧率:{fps}fps"); _sw.Restart(); } // ... 数据处理 }这个统计能帮你判断采集是否稳定。如果帧率波动超过10%,检查是不是回调处理太重或者PCIe带宽被其他设备抢了。稳定在标称帧率的95%以上才算合格。5.3 一个我踩过的坑:回调里别调SDK控制函数最后说一个我亲身翻车的经历。早期做项目的时候,我在回调里根据图像亮度动态调曝光,调的是SDK的CG300_SetExposure。结果程序跑几分钟就死锁,调试发现SDK的控制函数和回调在同一个线程上下文里,回调里再调控制函数就重入了,底层锁没释放。从那以后我每次做采集模块,都强制把控制指令和回调处理分成两个线程,用队列通信。回调只负责搬数据,控制线程负责调参数,中间用BlockingCollection传递指令。这个习惯帮我省掉了至少三次现场排查的时间。希望帮到你。本文还有配套的精品资源,点击获取