C#调用大恒CG300采集卡实现模拟视频采集实战

发布时间:2026/9/7 8:32:16
C#调用大恒CG300采集卡实现模拟视频采集实战 简介一份面向工业控制、图像处理与科学实验数据采集场景的C#开发资源围绕大恒CG300采集卡的软件二次开发展开。Demo包含设备初始化、四路独立通道数据采集以及C#窗体中的实时帧显示等关键功能既适合需要快速上手采集卡编程的初学者也可供有经验的开发者作为工程模板直接改造。针对资源说明中提到的“晶振改成28M”才能正常工作的问题Demo反映了硬件时钟配置与驱动程序的匹配思路能帮助读者避开同类硬件参数不匹配的采集异常。压缩包共28个文件约78KB内含C#源码工程、可执行程序、调试符号、资源文件及说明文档便于直接运行、断点调试和二次开发整体目录结构清晰虽然小巧但覆盖了从接口调用到界面展示的完整链路。目前已有489人浏览学习是一份实用性强、信息密度较高的采集卡开发参考。 上个月一个老客户找到我说车间里那台用了快十年的检测设备主机坏了换新电脑后发现原来的采集画面软件装不上去了。那台设备用的是一张大恒CG300采集卡负责把模拟相机输出的PAL信号转成数字图像送进软件里。客户的要求很直接新电脑上用C#重新写一个能采集、能显示、能保存图像的小程序。这次恰好把完整过程整理成一篇实战记录从SDK引入到画面显示再到各种坑一条线讲清楚给同样在用C#做上位机开发、或者准备接手模拟采集项目的人一份参考。文章里的代码你直接当模板用接口命名以手头SDK头文件为准但调用套路是一致的——无非是打开设备、启动采集、回调收图、关闭释放这四步。如果你是刚接触C#上位机开发或者正要接手类似的模拟采集老设备改造项目这篇文章足够帮你少走两个星期弯路。1. 先把项目想明白CG300采集卡能做什么C#又能做什么1.1 一张模拟采集卡在现代化上位机里的位置CG300采集卡属于模拟视频采集方案输入的是CVBS复合视频或者S-Video信号输出给电脑的是可以被程序读取的帧数据。用大白话说它就是一座桥把模拟摄像头那头几十年的老朋友接到数字世界这头的新电脑上。很多场景到现在仍然离不了它产线上那些没有数字接口的模拟CCD相机、安防监控里的模拟球机、医疗设备中的内窥镜信号都会通过CG300这类采集卡接入上位机。尤其是产线改造项目整体换相机的成本和产线停线时间很多人承受不了用采集卡把老信号接进新系统是最务实也最经济的过渡方案。1.2 为什么最终选了C#这条路如果只是做采集驱动SDK里自带的原厂Demo是C写的完成度也不低。但真实项目里采集只是最底层的一环后面还要做相机参数控制、图像算法、结果显示、数据入库甚至对接MES系统。C#在Windows桌面应用上开发效率高控件生态成熟Visual Studio调试体验也好做上位机主程序非常顺手。更关键的是CG300的SDK是以动态库方式提供的只要能从C#里通过P/Invoke调用它的导出函数就能和原生C程序一样完成所有采集操作。这个模式在工业视觉里太成熟了像海康的VisionMaster、VisionPro这些视觉软件在C#里做联合编程走的也是类似的路子。所以不是C#比C强而是它在“快速交付完整上位机”这件事上性价比最高。1.3 方案架构从上电到画面出现的完整数据流整个Demo的调用链路可以概括为六个动作初始化SDK、枚举设备、打开设备、注册回调、启动采集、在回调里收图。关停时就按相反顺序走一遍。后面所有代码都是围绕这条链展开的。这条流程里有一个隐性要求回调函数由SDK内部的工作线程触发所以回调里千万不能做耗时操作更不能直接操作UI控件。我的做法是在回调里只做字节拷贝把帧数据塞进一个队列界面线程用定时器去取队列里的最新帧来显示。这样回调的执行时间控制在微秒级采集不会丢帧界面也不会卡顿。2. 环境搭建与SDK引入最容易卡住新人的两个环节2.1 驱动安装与安装后的验证第一步不是写代码是把驱动装好。大恒CG300的SDK安装包从官网下载一般包含驱动、开发库、示例程序三部分。装完驱动后要在Windows设备管理器里确认“图像设备”或“大恒图像”分类下能见到采集卡设备。如果看到一个带黄叹号的未知设备说明驱动没装成需要重装一遍注意先用设备卸载工具清理干净再装否则新旧驱动残留容易互相干扰。这里有个很多人容易忽略的问题老卡驱动对操作系统版本很敏感。CG300这种服役好多年的型号官方驱动主要面向32位系统后来才慢慢补了64位兼容驱动。强烈建议在项目启动前先跟供应商确认当前系统版本是否在支持列表里能省去后面大量排查时间。我见过有人在Windows 10最新版上折腾三天装不上驱动最后换一台Win7老机器一次成功这种兼容性成本必须在项目规划时考虑进去。2.2 在C#工程中正确加载采集卡SDK拿到SDK后关注几个文件头文件里声明了所有函数和结构体bin目录下通常有32位和64位两个版本的DLLC#项目里用哪个取决于驱动支持哪个。CG300这类老采集卡的DLL大多是32位所以C#工程的“项目属性→生成→目标平台”必须设为x86。我第一次做这个项目时就踩过这个坑。直接用默认的AnyCPU编译程序一跑到DllImport加载函数就抛BadImageFormatException网上查一圈才发现是64位进程加载32位DLL被系统拒绝而采集卡核心DLL根本没有64位版本。把目标平台改成x86问题立刻消失。这种问题报错时机还很随机有时是打开窗体的瞬间崩有时是操作某个控件才崩因为DllImport的加载是延迟的要到第一次调用相关函数才真正发生。DLL文件放哪里也有讲究。最简单是放到程序输出目录也就是exe同一目录。如果SDK还依赖其他底层DLL或配置文件一起拷过来。调试阶段可以在“生成事件→后期生成事件命令行”里加一条拷贝命令避免每次手动复制。2.3 核心接口与数据结构的解读我不把SDK的完整头文件抄一遍只列必须弄明白的几类接口。不同版本SDK的命名会略有差异但结构基本一致初始化与反初始化一个Init、一个Uninit负责加载底层资源和释放全局句柄。设备打开与关闭OpenDevice和CloseDevice参数通常带设备索引和输出句柄。采集启停StartGrab和StopGrab一个开始出流一个停止出流。回调注册启动采集时传入一个函数指针SDK在每帧图像到来时调用它。图像信息结构体宽、高、Stride、像素格式、缓冲指针这是回调里最关键的一组参数。Stride这个词很多新手看不懂其实就是图像一行占用的字节数。因为内存要按4字节或8字节对齐Stride往往大于等于宽度乘以每像素字节数。用Bitmap转图像时如果忽略Stride画面会斜切花屏这个在3.3节会细讲。3. 核心功能实现采集Demo从打开设备到显示画面3.1 设备管理类打开与初始化直接给一个精简模板。用P/Invoke声明DLL函数后写一个CG300Device类把设备句柄包起来public class CG300Device : IDisposable { [DllImport(CG300Api.dll, CallingConvention CallingConvention.StdCall)] private static extern int CG300_Init(); [DllImport(CG300Api.dll, CallingConvention CallingConvention.StdCall)] private static extern int CG300_Uninit(); [DllImport(CG300Api.dll, CallingConvention CallingConvention.StdCall)] private static extern int CG300_OpenDevice(int index, ref IntPtr handle); [DllImport(CG300Api.dll, CallingConvention CallingConvention.StdCall)] private static extern int CG300_CloseDevice(IntPtr handle); [DllImport(CG300Api.dll, CallingConvention CallingConvention.StdCall)] private static extern int CG300_StartGrab(IntPtr handle, FrameCallback callback); [DllImport(CG300Api.dll, CallingConvention CallingConvention.StdCall)] private static extern int CG300_StopGrab(IntPtr handle); private IntPtr _handle IntPtr.Zero; private FrameCallback _frameCallback; public bool Open(int index) { if (CG300_Init() ! 0) return false; int result CG300_OpenDevice(index, ref _handle); return result 0 _handle ! IntPtr.Zero; } public void Start() { _frameCallback OnFrame; CG300_StartGrab(_handle, _frameCallback); } private void OnFrame(IntPtr data, int width, int height, int stride, int format) { // 回调逻辑只做入队不做UI操作 } public void Dispose() { CG300_StopGrab(_handle); CG300_CloseDevice(_handle); CG300_Uninit(); } }注意这段代码是按常见SDK接口命名写的使用前必须和头文件核对。错误码也好参数顺序也好一定要以你拿到的SDK版本为准。类里封装了句柄上层调用的时候就不用到处传IntPtr管理起来干净很多。3.2 启动采集与回调套路委托引用必须钉住Open方法里调用了Init这个动作对应整个SDK的全局初始化。规范做法是程序启动时调用一次Init结束时调用一次Uninit。如果代码里多次Open/Close要保证Init/Uninit也配对出现不配对会出现资源泄漏严重的会导致第二次打开直接失败。Start方法中把回调委托赋给私有字段是关键动作。为什么要这么写因为C#里委托在没有引用的情况下会被垃圾回收器回收。如果直接写CG300_StartGrab(_handle, OnFrame)委托作为参数传进去后可能立刻失去引用过一会儿GC一跑回调就再也不会来了。画面看起来就是“启动成功但没图像”进程内存还在涨但一张图都收不到。用一个字段把委托钉住是这类SDK封装的标准做法写注释时记得标清楚不然三个月后的自己也会踩一遍。回调函数签名里到底有哪些参数也必须以头文件为准。最常见的组合是数据指针、宽、高、Stride、像素格式有些版本还会带时间戳和帧号。时间戳在后续做帧率统计时很有用建议一并在回调结构体里保留下来。3.3 图像数据转Bitmap与界面显示回调传过来的数据是内存指针C#里不能直接当数组用。我的做法是先用Marshal.Copy把数据拷贝到托管字节数组再用LockBits写入Bitmap。转换代码private Bitmap ConvertToBitmap(IntPtr data, int width, int height, int srcStride) { int destStride width * 3; if (destStride % 4 ! 0) { destStride ((destStride / 4) 1) * 4; } byte[] buffer new byte[destStride * height]; for (int y 0; y height; y) { Marshal.Copy( IntPtr.Add(data, y * srcStride), buffer, y * destStride, destStride); } Bitmap bmp new Bitmap(width, height, PixelFormat.Format24bppRgb); BitmapData bd bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); try { Marshal.Copy(buffer, 0, bd.Scan0, buffer.Length); } finally { bmp.UnlockBits(bd); } return bmp; }逐行拷贝这段代码是很多画面斜切的根源。源图Stride往往不等于宽度乘3如果直接按总字节数平铺拷贝就会把每一行的对齐填充字节当成图像像素显示画面看起来就是一条条斜线拉过去。逐行拷贝虽然多写几行但稳。界面显示还有一个跨线程问题。回调线程不是UI线程直接在回调里给PictureBox赋值会抛“线程间操作无效”的异常。用定时器从队列取帧再在UI线程上刷新是最简单可靠的做法。刷新频率控制在25FPS左右就够了不用每帧都刷人眼根本分辨不出那点差别。3.4 关闭流程顺序比你想的重要关闭采集的顺序是先StopGrab停止出流再CloseDevice关闭设备最后Uninit反初始化。千万别图省事只调一个CloseDevice就完事也别在回调还在跑的时候直接Dispose队列。我碰到过同事图省事直接结束进程结果下一次打开设备时系统一直报“资源被占用”最后只能重启电脑才好。如果程序里用了多个采集通道关闭时要逆着打开顺序来后开的先关。这个原则类似于嵌套资源的释放反了就容易出内存访问异常。在Dispose里做得干净一些后面反复开关设备时才不会出幺蛾子。4. 踩坑实录与排查技巧这些问题我全都遇到过4.1 BadImageFormatException32位DLL和64位进程的经典冲突现象特别直白程序跑起来调用DllImport函数的瞬间抛异常。但真正迷惑的是这个异常出现的时机——如果你是在某个按钮事件里才第一次调用采集接口程序可能要运行半天才崩排查时很容易怀疑是业务逻辑问题。解决思路就一条确认DLL位数把C#工程目标平台强制设到对应架构。CG300这类老采集卡基本是x86的DLL目标平台直接选x86。另外注意在Visual Studio里“首选32位”那个勾选和“目标平台”是两个概念容易搞混。设置完重新编译再确认输出目录里的exe旁边确有SDK的所有DLL文件。4.2 回调不触发或图像出不来多半是委托和版本问题回调不触发第一个去查委托是否被GC回收这是最高频原因。第二个查驱动版本和SDK版本是否匹配。我遇到过驱动装的是新版DLL还是旧版结果回调一直不触发查了半天最后从事件查看器里看到底层驱动报错才锁定版本问题。SDK和驱动最好从同一安装包安装不要自己混搭。信号源也要排查。模拟采集卡的输入端没有接任何信号时启动采集后回调可能是正常的但图像数据是黑屏或雪花点。别把这个当成“设备没工作”用示波器或者换一台显示器确认信号源再说。如果回调频率明显偏低比如一秒才几帧大概率是信号制式不匹配PAL和NTSC别选错。4.3 画面卡顿、花屏和内存疯涨卡顿的主要原因是回调里做了耗时操作。处理图像算法、写日志、UI刷新这类动作一旦放进回调采集线程被拖住帧就会丢后续帧堆积整体延迟越来越严重。队列加定时器的方案能缓解但根本原则还是回调里只做内存拷贝。花屏上面提过优先查Stride是否逐行处理。再就是Bitmap的PixelFormat和采集数据的像素格式不匹配CG300输出的可能是YUV或特定排列的RGB转换前先确认。内存疯涨基本是Bitmap没有Dispose。PictureBox换图时旧图要释放定时器生成的新Bitmap用完要处理干净。用using包装可以省心一大半。如果队列缓存处理速度跟不上采集速度也要控制队列长度只保留最新一帧放弃旧帧否则队列会无限膨胀。4.4 设备识别不到的老大难设备管理器里看不到采集卡先排查硬件PCI卡是否插紧、插槽是否接触不良、主板有没有识别到新硬件。老卡对大容量硬盘和高负载PCI总线的兼容性也不是很理想有条件可以换个插槽试。驱动装好后还是看不到尝试用设备管理器“扫描检测硬件改动”或者完全卸载驱动后重新安装。代码层面不要写死设备索引为0。CG300支持多卡多通道要遍历设备索引逐个尝试打开。有些设备在系统里排在索引1甚至更高写死0就会一直报“设备不存在”。枚举不到设备时把错误码打出来和SDK头文件里的错误定义对一下能极大缩小排查范围。如果你看完准备动手我最后再强调两个点。第一设备管理器确认、DLL位数核对、委托引用保存这三件事解决了九成新手的启动问题。第二回调线程和UI线程的隔离一定要从一开始就做不要图省事在回调里刷界面否则后面加算法、加通信时你会非常难受。这条经验是几个凌晨三点换来的信我。等这条路走通了后面扩展扫码枪触发采集、串口通信、对接视觉算法库都是水到渠成的事。有更细的问题评论区留言看到会回。本文还有配套的精品资源点击获取