C#联合海康相机与雷赛运动控制卡实现产线ID识别与数据库上传 简介这份资源面向具备一定C#基础的工控与机器视觉开发者聚焦海康工业相机与雷赛运动控制卡的联合应用解决芯片数字识别后自动上传数据库的完整流程问题。压缩包共205个文件约22.98MB以39个cs源码文件为核心辅以resx、resources等界面资源dll、lib、h等依赖库以及config、xml、json等配置文件另有exe可执行程序、bmp与jfif图像素材和xlsx数据表覆盖从采集、识别到入库的完整工程结构。目前已有102人学习下载。读者可参考相机取像与运动控制卡协同工作的代码组织方式理解数字识别结果如何写入数据库并借助数据库查询模块掌握数据回读与校验思路工程内保留的日志与缓存文件也有助于排查运行期异常适合作为课程设计、设备上位机开发或视觉检测项目的源码学习范本。1. 一条产线追溯链的起点这套 C# 资源到底在解决什么产线上扫一个 ID相机拍照识别运动控制卡把工件送到位识别结果写进数据库——这条链路听起来简单真正落地时每个环节都能卡人。这套 C# 联合海康相机、雷赛运动控制卡识别 ID 上传数据库的资源包针对的正是这类工位级追溯场景用 C# 做上位机主控海康工业相机负责图像采集与 ID 识别雷赛运动控制卡负责轴运动与 IO 时序识别结果通过数据库持久化。适合做非标自动化、产线追溯、上位机开发的从业者尤其是需要把视觉、运动、数据三块拼到一起的人。它不是一个能直接跑起来的成品软件而是一套可拆解、可复用的工程骨架价值在于把三个子系统的调用方式、时序配合和数据落库路径摊开给你看。下面按「资源是什么 → 怎么用 → 坑在哪」的顺序拆。2. 海康相机 SDK 在 C# 里的取流与 ID 识别接入2.1 为什么选海康 MVS SDK 而不是 DirectShow海康工业相机MV 系列在 C# 上位机里的标准接入方式是官方 MVS SDK核心是MvCameraControl.Net.dll这套托管封装。热词里有人问「c# directshow uvc 回调里区分多个摄像头」那是 USB 摄像头走 UVC 协议的路子工业相机一般不这么用——海康 MV 系列走的是 GigE 或 USB3 VisionSDK 直接给到设备枚举、取流、参数配置的完整接口比 DirectShow 稳定得多也不会有多摄像头回调里分不清设备的问题。选型理由很直接工业现场要的是确定性帧率、曝光、触发模式都得能精确控制DirectShow 那套面向消费级摄像头的抽象在这里不够用。接入流程分四步枚举设备、创建句柄、打开设备、开始取流。下面是我一般会写的初始化骨架。// 海康 MVS SDK 相机初始化与取流 using MvCamCtrl.NET; using System.Runtime.InteropServices; private MyCamera _camera new MyCamera(); private bool _isGrabbing false; public bool InitCamera() { // 1. 枚举 GigE 和 USB 设备 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) return false; // 2. 用第一个设备创建句柄 IntPtr pInfo Marshal.UnsafeAddrOfPinnedArrayElement(deviceList.pDeviceInfo, 0); var devInfo (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure( pInfo, typeof(MyCamera.MV_CC_DEVICE_INFO)); nRet _camera.MV_CC_CreateDevice_NET(ref devInfo); if (nRet ! MyCamera.MV_OK) return false; // 3. 打开设备独占模式 nRet _camera.MV_CC_OpenDevice_NET( MyCamera.MV_ACCESS_Exclusive, 0); if (nRet ! MyCamera.MV_OK) return false; // 4. 注册回调开始取流 _camera.MV_CC_RegisterImageCallBackEx_NET(OnImageCallback, IntPtr.Zero); nRet _camera.MV_CC_StartGrabbing_NET(); _isGrabbing (nRet MyCamera.MV_OK); return _isGrabbing; }逻辑说明MV_CC_EnumDevices_NET把网口和 USB 口上的海康设备都列出来MV_CC_CreateDevice_NET用设备信息建句柄MV_CC_OpenDevice_NET的第二个参数是权限模式独占模式防止别的进程抢相机。回调注册用MV_CC_RegisterImageCallBackEx_NET图像数据在回调里拿到不要在回调里做耗时操作否则丢帧。参数说明MV_CC_OpenDevice_NET的MV_ACCESS_Exclusive是独占MV_ACCESS_Control是只控制不取流MV_ACCESS_Monitor是监听。产线上一般用独占。取流前如果相机是触发模式还得配TriggerMode和TriggerSource否则StartGrabbing之后一帧都不来。2.2 回调里做 ID 识别与结果提取图像回调拿到的是原始帧数据ID 识别通常有两种做法一是相机自带读码功能部分海康型号支持二是把帧转成Bitmap或Mat再走 OpenCvSharp 或第三方读码库。热词里「c# opencvsharp ordercorners」说明有人在做角点排序那多半是定位类任务ID 识别更常见的是条码/二维码解码。private void OnImageCallback(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pUser) { // 只处理单帧避免回调堆积 if (pFrameInfo.enPixelType ! MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8) return; int width pFrameInfo.nWidth; int height pFrameInfo.nHeight; byte[] buffer new byte[width * height]; Marshal.Copy(pData, buffer, 0, buffer.Length); // 转成 Bitmap 交给读码器 Bitmap bmp ByteToBitmap(buffer, width, height); string id DecodeId(bmp); // 读码逻辑可用 ZXing 或相机自带 if (!string.IsNullOrEmpty(id)) { // 抛到主线程或队列别在回调里写数据库 _resultQueue.Enqueue(new IdResult { Id id, Time DateTime.Now }); } }逻辑说明回调里只做数据拷贝和轻量解码识别结果丢进队列由后台线程消费。参数上PixelType_Gvsp_Mono8是黑白 8 位读码够用彩色帧数据量翻三倍回调压力大。nWidth、nHeight从帧信息里取不要写死。提示回调函数运行在 SDK 的内部线程上在里面直接操作 WinForm 控件会抛跨线程异常必须用Invoke或队列转出去。3. 雷赛运动控制卡的轴控与 IO 时序配合3.1 雷赛卡在 C# 里的调用方式与初始化雷赛运动控制卡DMC 系列、SMC 系列等在 C# 里通过官方提供的LTDMC.dll或对应型号的封装库调用本质是 P/Invoke 到原生 DLL。热词里「c# modbus tcp 客户端」是另一条路——有些雷赛卡支持 Modbus TCP但产线上更常见的是直接调 DLL响应更快、功能更全。选 DLL 的理由轴参数、IO 状态、插补指令都能直接拿到不用自己拼 Modbus 寄存器。初始化顺序打开卡、设脉冲当量、设加减速、使能轴。下面是我常用的初始化片段。// 雷赛 DMC 系列控制卡初始化 using csLTDMC; // 雷赛提供的 C# 封装 private ushort _cardNo 0; public bool InitMotionCard() { // 1. 打开控制卡0 是卡号 short ret LTDMC.dmc_board_init(); if (ret 0) return false; // 2. 设置 0 轴的脉冲当量多少脉冲走 1mm // 假设丝杆导程 5mm电机 10000 脉冲/转 double pulsePerMm 10000.0 / 5.0; LTDMC.dmc_set_equiv(_cardNo, 0, pulsePerMm); // 3. 设加减速起跳速度、最大速度、加速时间 LTDMC.dmc_set_profile_unit(_cardNo, 0, 10, // 起始速度 mm/s 200, // 最大速度 mm/s 0.2, // 加速时间 s 0.2); // 减速时间 s // 4. 轴使能 LTDMC.dmc_set_axis_enable(_cardNo, 0); return true; }逻辑说明dmc_board_init返回卡数量小于等于 0 说明没找到卡。dmc_set_equiv的第三个参数是脉冲当量算错这个轴就走不准这是最常见的翻车点。dmc_set_profile_unit用单位制设速度曲线比原始脉冲制直观。dmc_set_axis_enable使能后电机才锁死。参数说明脉冲当量 电机每转脉冲数 / 丝杆导程。如果电机带减速机还要乘减速比。起始速度别设 0否则启动会有冲击加速时间太短会丢步太长节拍上不去。3.2 视觉触发与运动到位的时序编排这套资源的核心难点不在单个模块而在时序工件到位 → 触发相机 → 识别 → 结果写库 → 卡继续走。常见做法是用 IO 触发相机而不是软件触发因为软件触发有延迟抖动。热词里「海康相机使用 io 触发模式并输出 ng/ok」说的就是这个场景。// 运动到位后触发相机等待识别结果 public bool MoveAndInspect(double targetPos, int timeoutMs) { // 1. 发绝对定位指令 LTDMC.dmc_pmove_unit(_cardNo, 0, targetPos, 1); // 1 绝对模式 // 2. 等轴到位dmc_check_done 返回 0 表示到位 int elapsed 0; while (LTDMC.dmc_check_done(_cardNo, 0) ! 0) { Thread.Sleep(5); elapsed 5; if (elapsed timeoutMs) return false; // 超时保护 } // 3. 到位后置 IO 输出触发相机拍照 LTDMC.dmc_write_outbit(_cardNo, 0, 1); // OUT0 拉高 Thread.Sleep(20); // 触发脉宽 LTDMC.dmc_write_outbit(_cardNo, 0, 0); // 4. 等识别结果超时算 NG return _resultQueue.TryDequeue(out var result, timeoutMs); }逻辑说明dmc_pmove_unit发定位dmc_check_done轮询到位状态轮询间隔 5ms 是精度和 CPU 占用的折中。到位后拉 IO 触发相机脉宽 20ms 保证相机能采到。识别结果从队列取超时判 NG。参数说明dmc_pmove_unit最后一个参数 1 是绝对定位0 是相对。dmc_write_outbit的第二个参数是输出口编号接相机触发线的那一路。超时时间按最坏节拍设一般给识别时间的 1.5 倍。注意IO 触发相机的接线要和相机端TriggerSource配置一致Line0 还是 Line1 接错了相机永远不触发这种问题查半天。4. 识别结果落库C# 写数据库的选型与批量写入4.1 数据库选型与连接方式热词里「c# 数据库」「数据库增删改查」「mysql 数据库连接池」都指向同一个问题上位机识别结果往哪写、怎么写。产线追溯场景一般选 MySQL 或 SQL Server轻量场景用 SQLite。选型看三点并发写入量、是否需要远程查询、现场有没有 IT 运维。单工位每秒几条记录SQLite 都够多工位汇总到 MES就得上 MySQL 或 SQL Server。C# 里连 MySQL 用MySqlConnector或MySql.Data连 SQL Server 用System.Data.SqlClient或Microsoft.Data.SqlClient。连接池是默认开的但要注意连接字符串里的Poolingtrue和Max Pool Size产线长时间跑连接泄漏会把池占满。// 用 MySqlConnector 写入识别结果 using MySqlConnector; private const string ConnStr Server127.0.0.1;Databasetrace_db;Uidop;Pwd***; Poolingtrue;Max Pool Size20;; public void SaveResult(IdResult result) { using var conn new MySqlConnection(ConnStr); conn.Open(); string sql INSERT INTO id_record (id_code, station, create_time) VALUES (id, station, time); using var cmd new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue(id, result.Id); cmd.Parameters.AddWithValue(station, ST01); cmd.Parameters.AddWithValue(time, result.Time); cmd.ExecuteNonQuery(); }逻辑说明using确保连接归还池。参数化查询防注入也避免拼接字符串的转义问题。Max Pool Size20按并发线程数设别设太大数据库端连接数也有限制。参数说明Poolingtrue默认就是开的显式写出来便于排查。AddWithValue简单但有类型推断问题严格场景用Add指定MySqlDbType。4.2 高频写入下的批量提交与事务单条INSERT在每秒几十条时就开始吃力热词里「c# sqlbulkcopy 表变动有影响」提示了批量写入的坑。SQL Server 有SqlBulkCopyMySQL 可以用MySqlBulkCopy或拼多值INSERT。产线上更稳的做法是攒一批再提交配合事务。// 攒批写入每 50 条或 500ms 提交一次 private ListIdResult _pending new ListIdResult(); private readonly object _lock new object(); public void EnqueueForBatch(IdResult r) { lock (_lock) { _pending.Add(r); if (_pending.Count 50) FlushBatch(); } } private void FlushBatch() { if (_pending.Count 0) return; using var conn new MySqlConnection(ConnStr); conn.Open(); using var tx conn.BeginTransaction(); try { string sql INSERT INTO id_record (id_code, station, create_time) VALUES string.Join(,, _pending.Select((_, i) $(id{i}, st{i}, tm{i}))); using var cmd new MySqlCommand(sql, conn, tx); for (int i 0; i _pending.Count; i) { cmd.Parameters.AddWithValue($id{i}, _pending[i].Id); cmd.Parameters.AddWithValue($st{i}, ST01); cmd.Parameters.AddWithValue($tm{i}, _pending[i].Time); } cmd.ExecuteNonQuery(); tx.Commit(); _pending.Clear(); } catch { tx.Rollback(); throw; } }逻辑说明攒批减少网络往返事务保证一批要么全进要么全不进。lock保护列表因为入队可能来自多个线程。参数名带下标避免重复。参数说明批量大小 50 是经验值太大单条 SQL 过长太小没效果。超时时间也要调默认 30 秒批量大时可能不够。提示SqlBulkCopy在表结构变动加列、改类型后会静默失败或错位用之前先校验目标表结构这是血泪经验。5. 避坑与排查这套链路最容易翻车的五个点5.1 相机取流正常但一帧都不到现象StartGrabbing返回成功回调不触发。原因相机处于触发模式但没给触发信号或者TriggerSource配错。解决先查TriggerMode是否为On是的话确认触发源软触发调MV_CC_TriggerSoftwareExecute_NET硬触发查接线和 IO 配置。5.2 轴走位偏差累积现象跑几十个循环后位置越偏越多。原因脉冲当量算错或加减速太猛丢步。解决重新核算脉冲当量含减速比把加速时间从 0.1s 放到 0.2s 以上起始速度别低于 5mm/s。5.3 数据库连接池耗尽现象跑几小时后报「too many connections」。原因某处MySqlConnection没using或异常路径没释放。解决全局搜new MySqlConnection确保都在using里连接字符串加Connection Lifetime300让池定期回收。5.4 回调里写库导致丢帧现象识别率时高时低帧率不稳。原因在图像回调里直接写数据库阻塞了 SDK 取流线程。解决回调只入队写库交给独立线程队列设上限满了丢最旧或报警。5.5 多相机时回调分不清设备现象两个相机回调数据串了。原因回调注册时pUser传了IntPtr.Zero没带设备标识。解决注册回调时把设备索引或句柄包成GCHandle传给pUser回调里解出来区分。6. 进阶把识别结果和运动轨迹绑成可追溯记录基础链路跑通后真正让这套资源有价值的是把「识别结果」和「运动轨迹」绑在一起。产线追溯不只是记一个 ID还要知道这个 ID 是在哪个位置、哪个时刻、哪个轴状态下识别的。我一般会在id_record表里加三列axis_pos识别时的轴坐标、cycle_no循环序号、image_path原图存档路径。这样出问题时能回放整个工位状态。-- 扩展后的追溯表结构 ALTER TABLE id_record ADD COLUMN axis_pos DECIMAL(10,3) COMMENT 识别时轴坐标, ADD COLUMN cycle_no INT COMMENT 循环序号, ADD COLUMN image_path VARCHAR(255) COMMENT 原图路径;写入时把dmc_get_position_unit拿到的坐标一起带上double pos LTDMC.dmc_get_position_unit(_cardNo, 0); var record new IdResult { Id id, Time DateTime.Now, AxisPos pos, CycleNo _cycleCounter, ImagePath SaveImage(bmp, id) // 按 ID 命名存图 }; EnqueueForBatch(record);验证方法跑 100 个循环查库看cycle_no是否连续、axis_pos是否和理论位置一致、image_path对应的图能否打开且 ID 可复现。三个都对链路才算稳。参数上axis_pos用DECIMAL(10,3)够到微米级image_path别存绝对路径存相对路径换机器不用改。存图会吃磁盘按天分目录定期清理。从那以后我每次接这类视觉加运动的工位都强制先把「到位 → 触发 → 识别 → 落库」这条时序在纸上画一遍标好每个环节的超时和失败分支再动手写代码。希望帮到你。本文还有配套的精品资源点击获取