ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Delphi7旧系统接入海康相机实现车牌识别:架构、代码与避坑

2026/9/8 9:56:48 拓冰建站 浏览量
Delphi7旧系统接入海康相机实现车牌识别:架构、代码与避坑 简介这是一份基于Delphi7实现的海康TCG-225摄像头预览与车牌识别工程面向需要快速对接海康网络摄像机、在Windows桌面端完成视频预览与车牌抓拍识别的开发者。工程完整实现了摄像头实时预览、手动抓拍、开启事件监听、监听抓拍事件并提取车牌号码等流程并针对监听功能所涉及的摄像头监听地址、接口配置及检测线绘制给出了实测可用的参考方式。压缩包共54个文件约14.25MB以海康SDK动态库、配套导入库为主同时包含Delphi源码文件、窗体与工程文件以及可直接运行的exe。动态库和导入库支撑底层采集与编码源码和工程文件便于理解事件回调与抓拍逻辑exe则可直接体验运行效果。目前已有1389人学习下载。项目虽然基于较早期的Delphi7环境但从预览、抓拍到车牌识别的事件链路清晰完整适合作为海康二次开发、Delphi调用网络SDK的入门参考也可将其中抓拍与识别逻辑迁移到新项目中复用。 仓库北门那台工控机用了十年系统还是Delphi7写的车辆进出登记程序今年换了台海康TCG-225摄像头需求很朴素实时预览画面上墙车到门口自动把车牌识别出来。听起来没什么新意真动手才发现老Delphi项目接新相机的坑全在细节里SDK没有官方Delphi封装识别算法也不可能塞进D7回调线程里稍不留神就闪退。我把这次改造的完整过程从头到尾捋一遍从SDK对接、预览出流、抓图到本地识别服务联调再到真实车流下的效果给同样困在老旧上位机项目里的朋友做个参考。即便你不写Delphi只想了解海康普通网络相机怎么接车牌识别后半段思路也能直接用。1. 手里这台TCG-225和铺了十年的Delphi7上位机1.1 为什么老掉牙的组合还在跑Delphi7是2002年的开发环境海康TCG-225算不上新设备但这两个凑在一起恰恰是不少中小企业、仓库、园区停车场的现状。老上位机里存着复杂的收费规则、客户档案、报表逻辑用C#或Go重写一遍成本太高只为了接一台摄像头就推倒重来老板不批。现实选择就是保住Delphi7主程序把视频预览、车牌识别当成新模块加进去。TCG-225这个型号我手头这台是标准的网络枪机具体分辨率、传感器参数我没专门去翻资料反正它支持海康标准网络SDK就足够了。这里想提醒一个新接触海康的人特别容易跑偏的点网上现在一搜海康开发铺天盖地是VisionMaster、VM脚本、智能相机那是另一条产品线的玩法。TCG-225这类普通网络相机走的是HCNetSDK.dll这一套老接口文章后面所有内容都是基于这条线。方向先定准后面才不会被带偏。1.2 动手前先想清楚的两个关键点第一车牌识别到底放哪里做。我一开始也想直接在Delphi里调用识别DLL搜了一圈发现现在好用的开源车牌识别几乎全在Python生态要么是带一堆依赖的C推理库想在D7里集成纯属给自己上刑。最后定的架构是海康SDK只负责取流和抓图识别交给本地Python服务Delphi通过HTTP调用老程序改动最小。这个决策当时看只是省事后面发现还有个隐性好处识别模型升级完全不用动上位机。第二实时预览走哪种方式。海康SDK给预览提供了两条路把窗口句柄直接交给SDK渲染或者注册回调自己处理码流。做车牌识别这种项目画面显示用第一条路就够回调那条路在Delphi里踩起来非常痛第二章节细说。这两个问题想清楚了整个项目的开发量基本就定型了。2. 把海康网络SDK装进Delphi7封装和出流的真实步骤2.1 HCNetSDK动态库的手工Delphi移植海康官方SDK安装包里没有Delphi目录网上流传的HCNetSDK.pas版本普遍偏老新SDK里的函数在里面根本找不到。我的做法是不迷信网上那个完整单元自己照着C头文件声明一个精简版只声明项目里要用到的函数模块少反而不容易出错。这里有两个细节必须留意。一是函数调用约定海康SDK导出函数基本都是stdcallDelphi里声明时必须写stdcall漏掉会从栈里随机取参数程序当场崩。二是结构体对齐C语言结构体默认按自然边界对齐Delphi的默认对齐模式大部分情况下能对上但像NET_DVR_USER_LOGIN_INFO这种几十个字段的结构体必须逐个字段核对类型和长度不能用错整数类型。比如设备地址是char[129]在Delphi里就是array[0..128] of AnsiChar不是PChar一用错后面所有字段全错位。新SDK推荐NET_DVR_Login_V40和NET_DVR_RealPlay_V40登录和预览的结构体都换过了。网上能找到的老代码多是NET_DVR_Login_V30、NET_DVR_RealPlay只要固件版本兼容也能用但我统一用V40接口少一个版本兼容的判断。2.2 登录、预览、出画面的最小代码骨架如果只是把画面推到一个Panel上代码量其实不大初始化、连接超时设置、登录、预览四步就够var hUserID: LongInt; lpLoginInfo: NET_DVR_USER_LOGIN_INFO; lpDeviceInfo: NET_DVR_DEVICEINFO_V40; hRealPlay: LongInt; PreviewInfo: NET_DVR_PREVIEWINFO; begin NET_DVR_Init; NET_DVR_SetConnectTime(2000, 1); FillChar(lpLoginInfo, SizeOf(lpLoginInfo), 0); StrPLCopy(lpLoginInfo.sDeviceAddress, 192.168.1.64, 128); lpLoginInfo.wPort : 8000; StrPLCopy(lpLoginInfo.sUserName, admin, 63); StrPLCopy(lpLoginInfo.sPassword, your_password, 63); hUserID : NET_DVR_Login_V40(lpLoginInfo, lpDeviceInfo); if hUserID 0 then begin FillChar(PreviewInfo, SizeOf(PreviewInfo), 0); PreviewInfo.lChannel : 1; PreviewInfo.dwStreamType : 0; // 0主码流1子码流 PreviewInfo.dwLinkMode : 0; // 0 TCP方式 hRealPlay : NET_DVR_RealPlay_V40(hUserID, PreviewInfo, Panel1.Handle, nil, nil); if hRealPlay -1 then ShowMessage(预览失败错误号: IntToStr(NET_DVR_GetLastError())); end else ShowMessage(登录失败错误号: IntToStr(NET_DVR_GetLastError())); end;注意NET_DVR_RealPlay_V40的第三个参数是窗口句柄把Panel1.Handle传进去SDK就自动把画面渲染到Panel上。登录失败一定要用NET_DVR_GetLastError()拿错误码排查最常见的122是账号密码错117是网络连不上直接弹错误码比瞎猜快很多。2.3 回调预览和窗口预览怎么选窗口预览最省心解码、渲染全在SDK内部完成Delphi只是挂个句柄CPU占用低也基本不会拖慢老工控机。代价是拿不到帧数据没法做自由OSD叠加。回调预览则是自己接收实时码流自由度大但要在Delphi7里自己解码、转格式、绘制到界面播放库输出的可能还是私有格式还得再调海康的解码库。对车牌识别项目来说预览是给人看的识别走独立抓图接口完全不需要从预览流里抠帧所以窗口预览绝对够用。当初有人劝我用回调拿帧去识别更实时实测下来是个坑后面抓图接口返回JPEG更好用也不用跟编码格式纠缠。对比项窗口预览推荐回调预览实现成本很低传句柄就有画面高要自己解码、转换、绘制CPU占用低SDK内部解码中等偏高还要做额外格式转换拿帧数据拿不到能拿到原始码流适用场景本项目人看画面独立抓图识别录像分析、自定义帧处理3. 车牌识别不在Delphi里硬扛本地识别服务的拆法3.1 为什么识别要单独放一个服务在Delphi7进程里直接做深度学习推理几乎每个环节都难受依赖库要用VC重新编译版本还不一定兼容D7的内存管理器对大块张量分配支持差上GPU推理要桥接CUDA更是难上加难。把识别放到本地服务之后Delphi只管三件事抓图、把图片传过去、收文字结果整个系统的复杂性瞬间降了一个量级。拆成服务还有一层部署上的好处。服务可以跑在门卫这台工控机上也可以单独放一台小主机统一处理多路相机的识别算法有新版模型服务端一换就完事老上位机一行代码都不用改。对于维护中的老项目来说这个隔离价值非常大。3.2 用Python搭一个HTTP车牌识别接口识别服务我用的方案是Python Flask HyperLPR几百KB的纯Python库不需要GPU也能跑。现场对复杂场景要求更高的话也能换PaddleOCR的车牌模型接口层不动。服务端只暴露一个POST接口接收图片返回车牌号、颜色和置信度from flask import Flask, request, jsonify from hyperlpr3 import HyperLPR3 app Flask(__name__) engine HyperLPR3() app.route(/plate_recognition, methods[POST]) def plate_recognition(): image_data request.files.get(image) if image_data is None: return jsonify({code: 1, msg: no image}) result engine.recognize(image_data.read()) if result and result.plates: plate result.plates[0] return jsonify({ code: 0, data: { plate: plate.text, color: plate.color, confidence: float(plate.confidence) } }) return jsonify({code: 2, msg: not found}) if __name__ __main__: app.run(host0.0.0.0, port5000)上面代码是示意HyperLPR3各小版本的返回对象字段名略有差别用的时候以安装版本带的示例为准。服务跑起来后先用浏览器或Postman手动传几张图片验证能正确识别出车牌再继续联调否则后面分不清是抓图问题还是识别问题。如果现场有多路相机同时请求建议把app.run的threaded参数打开或者接一个任务队列避免图像排队阻塞。3.3 Delphi端抓图和上传识别的实现抓图用海康SDK的NET_DVR_CaptureJPEGPicture把JPEG写到内存缓冲区。这个老接口有一个坑点它不返回实际写入的字节长度所以拿到缓冲区之后要先找JPEG的结束标记FFD9截断再转Base64传走。这个细节网上资料很少不少人就是栽在缓冲区长度上。var JpegBuf: array[0..1024 * 1024 - 1] of AnsiChar; jpgEnd: Integer; jpgBytes: TBytes; b64: string; Http: TIdHTTP; Stream: TStringStream; Resp: TStringStream; begin FillChar(JpegBuf, SizeOf(JpegBuf), 0); if not NET_DVR_CaptureJPEGPicture(hUserID, 1, JpegBuf, SizeOf(JpegBuf)) then Exit; // JPEG以FFD8开头、FFD9结尾扫描到FFD9认为图片结束 jpgEnd : 2; while jpgEnd SizeOf(JpegBuf) - 1 do begin if (Byte(JpegBuf[jpgEnd]) $FF) and (Byte(JpegBuf[jpgEnd 1]) $D9) then Break; Inc(jpgEnd); end; SetLength(jpgBytes, jpgEnd 2); Move(JpegBuf[0], jpgBytes[0], jpgEnd 2); b64 : EncodeBase64(jpgBytes); Http : TIdHTTP.Create(nil); Stream : TStringStream.Create({image: b64 }); Resp : TStringStream.Create(); try Http.ConnectTimeout : 3000; Http.ReadTimeout : 5000; Http.Post(http://127.0.0.1:5000/plate_recognition, Stream, Resp); // 到这里Resp.DataString就是识别服务返回的JSON finally Resp.Free; Stream.Free; Http.Free; end; end;图片用Base64塞进JSON里对Delphi7来说最简单不用拼multipart头、不用处理文件流边界。1MB左右JPEG的Base64会大三分之一局域网里传起来问题不大。3.4 识别结果的解析和车牌在界面上显示服务端返回JSONDelphi端推荐直接用SuperObject这个纯pascal库解析D7能编译解析很顺手。取出数据后把车牌、颜色、置信度刷新到界面并把当次抓图存到本地留档。UI上我额外加了两个标签最近识别时间和识别失败次数。频率控制也放在这一层车没来不要一直抓图。我最初图省事定时两秒抓一次结果门卫说识别列表里经常是外面路过车的车牌后来改成有触发再抓图效果立刻正常。现场条件不允许加外部触发的话至少把抓图间隔拉长到五秒以上别让无效抓拍刷屏。4. 联调三座山闪退、识别不出来、界面假死4.1 在回调线程里做重活不出五秒就崩第一次联调时图省事直接在预览回调里做抓图和HTTP上传逻辑上没错可程序开着不到十秒就闪退。根因是海康的回调线程是播放库内部线程在里面做Base64编码、内存分配、HTTP阻塞这类重活等于把解码线程卡死Delphi老内存管理器对多线程频繁分配又敏感最后只能以崩溃收场。正确姿势是回调里只干一件事PostMessage通知主窗口让主线程去调度所有重操作放到工作线程里。逻辑就变成播放库通知你出画面了你按自己的节奏去处理。识别完成后通过同步控件回到界面线程刷新预览和识别互不拖累程序跑一天都不会崩。4.2 夜间抓图黑成一团识别率直接腰斩联调白天一切正常一到傍晚识别率掉得没法看。排查时我先用海康官方测试工具单独抓图发现抓出来的JPEG偏暗没补光的区域更是模糊一片。这问题不在识别算法在采集端。解决分三步第一把相机日夜转换打开配置成按光线自动切红外第二抓图质量参数调高海康抓图接口对质量有参数支持但默认值经常偏低实测调到90以上效果明显第三在识别服务端加一个简单的预处理图片亮度均值低于阈值就做一次自适应增强对HyperLPR这类模型帮助很大。这套组合下来夜间的识别率能追回不少。4.3 HTTP等待把界面卡死线程化和超时是解药最早版本在主线程里直接做HTTP调用识别服务一旦处理慢界面就卡成PPT。因为TIdHTTP.Post是同步的用户操作一下得等服务响应完界面才恢复。改进方案是彻底线程化写一个工作线程类抓图、编码、上传、解析全在线程里做主界面只通过Windows消息接收结果。同时给TIdHTTP设置ConnectTimeout和ReadTimeout识别服务挂了能立刻报识别服务不可用而不是无限等下去。还有一个容易被忽略的细节识别服务失败时不要马上重试。我给失败做了计数连续三次失败就暂停一分钟。否则服务端刚抽风还没恢复这边疯狂重试反而把网络打满把问题放大。5. 真实车流下的效果和还能往上加的东西5.1 实测数据识别率和延迟的参考范围这套方案在仓库门口跑了一个多月说下实际效果。白天正常光照下车牌识别率在95%左右车牌倾斜太厉害或者被泥污挡住的情况会漏晚上开补光后能到85%到90%比白天略低。从车辆停稳到界面弹出车牌号网络通畅时大约0.5到1秒主要开销在抓图接口往返和识别推理。识别服务跑在一台i5工控机上单张推理基本控制在200毫秒以内。想稳定达到这个效果相机安装角度非常关键。我第一次装得偏高俯视角度太大会让车牌严重变形识别模型再好也有极限。后来把相机高度降到离地2米左右车牌在画面里占宽度三分之一以上识别率立刻提升一个档次。这个经验比任何参数调整都管用。5.2 后续扩展记录、统计、道闸联动这套架构跑通之后往上加功能很容易。我在Delphi端加了ADO连Access的本地库每次识别记录都存下来再配合Excel导出月度报表。道闸联动用的是串口继电器控制识别到白名单就发开闸指令识别失败走人工登记。这些都是老程序原本具备的能力只是把手动输入车牌换成了相机自动带出来。如果以后现场要加多路相机识别服务可以继续挂在同一台机器上Delphi端加几个通道配置就行。整条链路里最容易出问题的始终是网络和光线把这两个变量提前控制住老系统接新硬件这件事就不会太难。本文还有配套的精品资源点击获取