简介:这是一套面向物流仓储、码头及工厂过磅管理场景的Java企业级称重系统源码,适用于具备Java Web与桌面应用开发基础的中级以上开发者学习与二次开发。系统深度融合海康威视摄像头(实现实时视频监控与过磅抓拍)与芊熠摄像头(完成高准确率车牌识别),打通称重数据、图像证据与车辆身份信息,解决传统过磅中人工登记易错、过程不可溯、数据难关联等核心痛点。资源包共483个文件,含219个Java后端逻辑文件、26个FXML桌面界面文件、44个JavaScript前端交互脚本、39个XML配置与38个CSS样式文件,以及33个DLL驱动库(支撑硬件通信),整体压缩后41.25MB;前端采用Bootstrap、FullCalendar、DataTables等主流组件,风格统一且可扩展性强。目前已有723人下载学习,提供完整模块化结构(如ewcrm-service、ewcrm-dao、ewcrm-fx等清晰分层)、服务器端数据持久化方案及后台编辑查看能力,开箱即可运行并快速对接实际业务场景。 做过地磅系统的人都知道,这套东西看起来简单,真正落地全是细节。我最初接这个项目时,甲方就一句话:"你们帮我把车牌识别、称重记录、数据上传这些串起来,以前都是人工填单子,太慢了。"听起来不算复杂,结果从海康威视SDK的JNA封装、芊熠车牌识别相机的HTTP接口调试,到仪表串口数据稳定读取,再到光电隔离、道闸联动,整个周期将近三个月才敢说稳定运行。这篇就把整个系统的设计思路和源码实现串起来讲一遍,包括那些踩过的坑。
这套系统本质上解决的是:车辆驶上地磅后,系统自动识别车牌、自动读取仪表重量、自动生成称重记录,并把抓拍图片和过磅数据绑定存档,全程不需要人工填单。核心是Java后端,集成了海康威视摄像头做监控与图片抓拍,用芊熠车牌识别相机做车牌号的自动识别,最后落库并向第三方ERP/MES系统推送。适合正在做无人值守称重、智能物流、工厂进出厂管理的开发者参考,尤其是第一次接触海康SDK或者芊熠相机HTTP协议的人。
1. 为什么过磅系统要集成车牌摄像头:传统地磅的痛点与改造思路
1.1 传统过磅流程的三宗罪
没有摄像头的传统地磅,过磅流程基本是这样:司机把车开上磅,司磅员在电脑上手动选择"空车"还是"重车",输入车号,等仪表数字稳定,手抄毛重或皮重,最后打印一张磅单。这个流程最大的问题不是慢,而是错。
第一,车号登记靠问。司机报个牌号,司磅员输进系统,字输错了,后面对账全乱。第二,作弊空间大。车不完全上磅、压边、跳磅这些操作,单靠人眼很难抓。第三,纠纷说不清。货主说这车数据不对,但系统里没有现场照片,拿不出证据。所以后来很多地磅改造项目,摄像头就成了标配。
1.2 集成摄像头后,一个完整的自动过磅流程长什么样
我做的这套系统,落地后的完整流程是这样的:
车辆驶入磅区,地感线圈或红外对射触发后,芊熠车牌识别相机抓拍车头,识别出车牌号,同时海康威视球机抓拍一张全景照片。系统判断当前是"上磅"动作,开始等仪表重量稳定。稳定后把重量和车牌绑定,生成一条过磅记录。如果车还在系统车辆档案里,自动带出常用皮重,直接算出净重;不是内部车辆就走"登记-上磅-下磅"两次称重流程。最后数据推送ERP,同时把磅单图片、抓拍照片一并归档。全程不需要司磅员碰键盘。
对比一下传统流程,单次过磅时间可以从两分钟左右压缩到二十到三十秒,更重要的是数据可追溯,图片和记录绑定,出问题能倒查。
1.3 海康威视和芊熠摄像头分别承担什么角色
这个项目里我用的是两套摄像头协同,不是二选一。
海康威视负责的是"看得见、留得下、查得到"。它接入现场监控大屏,实时预览,对磅台全景和车尾等进行抓拍。我用的海康摄像头是IPC球机,布防报警后可以联动抓拍。这部分主要依赖海康HCNetSDK,通过JNA把C接口封装成Java能调用的方法。
芊熠负责的是"看得准"。它是专门的车牌识别一体机,直接输出结构化结果,比如车牌号、车牌颜色、识别时间,不用我们自己跑算法。和传统OpenCV识别方案比,芊熠的优势是识别速度快、夜间红外补光效果好、误判率低,而且对外暴露的是HTTP JSON接口,Java对接非常方便。
两套相机在逻辑上完全独立,但业务上必须联动:芊熠识别到车牌号,海康抓拍到现场图,后台按同一时间戳和磅单号关联存储。
2. 系统整体架构与模块划分:不止是"称重+拍照"那么简单
2.1 硬件层:地磅仪表、道闸、红绿灯、摄像头的协同
先列一下这套系统里涉及的硬件设备,做后端开发的人常常忽略这一层,结果到现场才发现接口对不上。
- 地磅仪表:我用的耀华XK3190-A9,串口输出连续重量数据
- 车牌识别相机:芊熠T260系列,通过网口输出HTTP JSON
- 监控相机:海康威视IPC,通过RTSP+HCNETSDK对接
- 道闸控制器:遥控开关量输入,部分场景接入继电器控制
- 红绿灯:提示车辆可以上磅 / 称重完成可以下磅
- 地感线圈或红外对射:判断车辆是否完全上磅
硬件层设备通过交换机接入同一个局域网。这个网段规划很重要,我项目里就吃过亏:海康相机默认IP是192.168.1.64,芊熠默认是192.168.1.120,如果不提前规划,到现场装完才发现全冲突了。
2.2 Java后端分层设计与关键依赖
后端是Spring Boot + MyBatis Plus架构,Maven多模块项目。我按"对接层、业务层、管理后台"三层来拆:
weigh-api:对外接口模块,给ERP、MES推送称重数据weigh-core:核心业务模块,过磅流程、数据校验、重量稳定判断weigh-camera-hikvision:海康SDK封装模块weigh-camera-qianyi:芊熠HTTP协议对接模块weigh-serial:串口仪表通讯模块weigh-admin:管理后台,车辆档案、记录查询、磅单打印
这个过程我用了JNA调用海康SDK,用了RXTX和jSerialComm做串口通讯,数据库用的MySQL,缓存用的Redis。为什么用JNA而不是海康官方Java SDK?因为海康官方在较早时期对Java的支持是通过JNA demo实现的,而且自己管理DLL动态库反而更灵活,交叉编译、多平台部署都好控制。jSerialComm则是因为它比RXTX在Windows下更稳定,串口热插拔不容易崩。
2.3 数据库表设计:称重记录、车辆档案、摄像头配置
核心就是三张表:车辆档案表、称重记录表、摄像头配置表。
车辆档案表保存车牌号、车辆类型、常用皮重、所属单位、设备绑定关系。称重记录表是主表,一次上磅生成一条记录,字段包括流水号、车牌号、毛重、皮重、净重、称重时间、方向(进/出)、操作方式(自动/人工)、抓拍图片路径、识别图片路径、状态。摄像头配置表保存每一台相机的IP、端口、用户名、密码、相机类型(海康/芊熠)、安装位置(入口/出口)、是否启用。
我当时设计称重记录表的时候特意加了weigh_status字段,用来表示当前这条记录处于哪个阶段:1上磅待识别、2已识别待稳定、3称重完成、4已推送。这个状态字段帮了大忙,因为现场断网、推送失败时,可以通过定时任务补推。
索引上有一个坑要提醒:车牌号、称重时间这两个字段必须建联合索引,否则量大了以后查询磅单列表会非常慢。我见过一个项目,称重记录表才二十万行,查一天的数据要十几秒,原因就是没索引。
3. 海康威视摄像头接入:从SDK初始化到车牌抓拍的完整链路
3.1 海康SDK的初始化与登录
海康摄像头接入的底层是HCNetSDK.dll,通过JNA封装。初始化很简单:
HCNetSDK hcNetSdk = Native.load("HCNetSDK", HCNetSDK.class); hcNetSdk.NET_DVR_Init();但是登录比较关键,必须用NET_DVR_Login_V40,而不是老的NET_DVR_Login。因为V40兼容性好,返回的登录句柄支持后续报警布防和抓图。如果项目用的是老设备,只有NET_DVR_Login能登录,那也没办法,我看现场情况来切换。
登录成功后,保存lUserID句柄,后续所有操作都靠它。这里有个细节:海康SDK每次初始化只能做一次,多次调用NET_DVR_Init()会导致资源泄漏。项目启动时初始化一次,关闭时调用NET_DVR_Cleanup()释放。
还有一点,海康不同型号的相机,SDK版本要求不一样。我项目里同时有DS-2CD系列和DS-2DE系列球机,就必须用比较新的SDK版本,否则球机的一些云台功能调不了。发布的时候SDK的dll、配置文件HCNetSDKCom文件夹都要放到Java项目的执行目录下,否则运行时会报加载失败。第一次现场部署时就是因为漏了这个,程序启动报找不到hlog.dll,查了半天才发现SDK组件目录没一起打包。
3.2 实况预览与抓图
实时预览在Java里最省事的方案是直接通过RTSP拉流,用VLC或者JavaCV显示。但是抓图,也就是拍照取证,还是要走SDK。
抓图有几种方式:NET_DVR_CapturePicture、NET_DVR_CaptureJPEGPicture。我建议用NET_DVR_CaptureJPEGPicture,因为它是直接编码成JPEG,参数可控,不需要单独处理BMP转JPEG:
NET_DVR_JPEGPARA jpegPara = new NET_DVR_JPEGPARA(); jpegPara.wPicSize = 0xff; // 图片大小按实际配置 jpegPara.wPicQuality = 0xff; boolean result = hcNetSdk.NET_DVR_CaptureJPEGPicture( lUserID, 1, jpegPara, picturePath.getBytes(), 1024 * 1024);注意抓图路径在Windows下要用GBK编码,Linux下用UTF-8,这个不同平台字节码差异很容易踩坑。我后来统一用ByteUtil.getStringBytes封装了一个方法去兼容。
3.3 海康车牌识别的结果解析
海康部分设备本身自带车牌识别功能,通过报警布防实时回调车牌信息。通过NET_DVR_SetupAlarmChan_V41注册报警回调,当车辆经过相机视域时,SDK回调ALARM_VC_PLATE消息,里面带有结构化车牌数据。
回调函数里要解析的是NET_DVR_PLATE_INFO结构。这个结构里主要字段有:
sLicense:车牌号字符串byColor:车牌颜色,0蓝色、1黄色、2白色、3黑色byType:车牌类型struPlateRect:车牌在画面中的位置矩形
不过说实话,海康的车牌识别结果我用得不多,因为项目里的主识别设备是芊熠。海康这个回调更多是作为一个"备胎"方案。如果现场芊熠相机故障,自动切换走海康识别。这个双保险机制后面详说。
回调函数是海康SDK在它自己的线程里调用的,注意不要在里面做耗时操作,比如数据库写入、HTTP请求。一定要把数据拷贝出来,扔到自己的线程池里处理。我第一次写的时候直接在回调里调了MyBatis插入,结果SDK线程卡死,报警消息全都不往后走了,设备端都被打满了。
3.4 常见坑:SDK内存泄漏、回调阻塞、网络超时
海康SDK接起来容易,稳定运行难,说说我实际踩过的坑。
第一个是内存泄漏。海康SDK的NET_DVR_Init和NET_DVR_Cleanup不能在业务代码里频繁调用,每次登录和登出也要保持间隔。我项目早期版本每次抓图前都重新登录设备,结果跑了几天,Java进程内存直接爆掉。解决方式是做一个设备连接管理器,启动时建立连接,长连接保活,断线才重连。
第二个是回调阻塞。海康报警回调线程是内核态线程,如果阻塞了,所有报警都会积压。实际做法是把回调里收到的数据对象浅拷贝出来,丢到ExecutorService处理,回调方法立即返回。
第三个是网络超时。海康相机的RTSP流和SDK信令都在同一个网口,如果现场网络环境差,设备经常掉线。一定要做心跳检测和自动重连。我写了一个定时任务,每隔10秒检查一次NET_DVR_GetDVRWorkState,连续失败3次就重新登录。
4. 芊熠摄像头接入:为什么选择它 + 它的JSON接口实现
4.1 芊熠车牌识别相机的工作原理
芊熠车牌识别一体机本质上是一台带算法的小型嵌入式设备,前端集成了摄像机、补光灯、识别算法。它与海康那种通用相机不一样,不需要你在后端跑识别模型,相机在本地就完成了车牌定位、字符分割、字符识别,然后把结果通过网口推给你。
相机工作方式有两种:一种是主动推图模式,相机检测到车辆进入识别区域后,主动把识别结果POST到后台服务器;另一种是请求响应模式,后台按需向相机请求结果,或发送图片给相机做识别。
我项目里用的是第一种主动推图模式。相机内配置好后台接口地址,每当有车通过,芊熠相机就向后台发送一个HTTP POST请求,请求体是JSON。
4.2 HTTP/JSON接口调用细节
芊熠相机主动上传的JSON格式大致是这个样子:
{ "plateNo": "粤B12345", "plateColor": "blue", "plateType": "standard", "time": "2025-03-15 14:23:05", "image": "http://192.168.1.120/capture/202503151423056789.jpg", "imageBase64": "", "bright": 12, "vehicleType": "truck", "schedule": 45 }字段不固定,不同固件版本会差异。我踩过的坑是,旧版相机返回的plateColor是1、2这种数字枚举,新版相机直接返回blue、yellow字符串。所以解析的时候不能写死枚举映射,我给这个字段写了两种解析策略。
后端接收接口我用的Spring Boot的@RestController。做了一层签名校验加IP白名单,因为相机是固定IP的,只允许局域网内那几台相机IP访问,保证安全。
@PostMapping("/api/camera/qianyi/notify") public Result<String> receivePlateNotify(@RequestBody QianYiPlateNotify notify) { // 校验来源IP String clientIp = request.getRemoteAddr(); if (!allowedCameraIps.contains(clientIp)) { return Result.fail("ip not allowed"); } // 异步处理过磅业务 weighService.asyncProcessPlate(notify); return Result.success("ok"); }有个细节:相机的HTTP请求超时时间很短,如果后台处理超过2秒,相机会重传。所以接口里一定不能同步去处理完整过磅逻辑,必须异步。我当时用Redis锁做了个去重,防止相机重传导致同一辆车生成两条称重记录。
4.3 识别结果关联的图片处理
芊熠相机返回的消息里带了一个图片URL,指向相机自身存储的抓拍图片。这里有个很实用的技巧:不要直接把相机的URL存到数据库,因为相机的存储空间有限,过几天图片可能被覆盖掉。应当后台主动去相机拉取图片,保存到本地文件服务器或对象存储,再把路径写入称重记录表。
图片下载可以直接用HttpClient:
byte[] imageBytes = httpClient.get(notify.getImage()); String localPath = saveImageToLocal("plate_" + notify.getPlateNo() + "_" + timestamp + ".jpg", imageBytes);保存图片的同时,我还会从海康相机抓一张全景图,这就回到了前面说的双摄像头协同。车牌识别图负责证明"车是谁",海康全景图负责证明"现场环境是什么样"。两个图片在称重记录表里分别用plate_image_path和scene_image_path字段保存。
5. 从"识别到车牌"到"完成称重记录":核心业务逻辑串起来
5.1 过磅事件状态机
整个过磅业务流程就是一台状态机。我一开始没画状态机,直接if else写,后面代码越来越乱,重构时理了一下,核心状态就四个:
WEIGHING:车辆已上磅,重量数据正在读取中PLATE_CONFIRMED:车牌已识别,等待重量稳定WEIGHT_STABLE:重量稳定,可以生成记录COMPLETED:记录已生成并推送第三方系统
在实际代码里,我用枚举+状态表驱动流转。每次芊熠回调、重量读表、重量稳定都会触发状态变更。流转顺序不一定完全相同,比如外部车辆可能先生成毛重记录,下磅后再生成皮重记录,中间跨了很长时间。
这个状态机设计有个好处:就算某个环节出错了,比如识别失败,状态卡在WEIGHING,后台定时任务可以扫出那些停留超过5分钟的状态记录,触发人工介入流程。
5.2 重量数据采集和稳定性判断
重量数据来自仪表串口。耀华仪表的连续输出格式大概是这样的:
=+000123.4kg\r\n =+000123.5kg\r\n用串口读取程序解析每帧数据。重量稳定不能只看一次读数,我采用的方法是:连续采集10次,如果这10次的重量值最大和最小差值小于等于一个阈值(比如10公斤),就认为重量稳定。这个阈值可以配置,毕竟不同地磅量程不一样。
public boolean isStable(List<Double> weights, double threshold) { double max = Collections.max(weights); double min = Collections.min(weights); return (max - min) <= threshold; }串口这块是个重点。我最早用RXTX,结果在Windows上总是随机丢帧,后来换jSerialComm才好。另外,串口读取必须保证线程安全,一次只允许一个线程读取。我们项目的重量读取和状态判断放在同一个线程里,采用生产者消费者模式:串口读取线程作为生产者,重量数据放到队列;业务逻辑线程作为消费者。
5.3 一次过磅的核心代码流程
把前面所有模块串起来,一次标准的过磅处理流程大致是:
public void processPlateNotify(QianYiPlateNotify notify) { // 1. 检查当前是否有进行中的称重记录 WeighRecord record = weighRecordMapper.selectOngoingByPlate(notify.getPlateNo()); // 2. 如果没有进行中的记录,创建一条新记录,状态设为WEIGHING if (record == null) { record = createNewRecord(notify.getPlateNo(), Direction.IN); } // 3. 从串口读取当前重量 double currentWeight = serialService.readCurrentWeight(); // 4. 判断重量是否稳定 if (weightStableChecker.isStable()) { // 5. 如果当前记录还没有毛重,写入毛重;否则写入皮重 if (record.getGrossWeight() == null) { record.setGrossWeight(currentWeight); } else { record.setTareWeight(currentWeight); record.setNetWeight(calculateNetWeight(record)); record.setStatus(WeighStatus.COMPLETED); } // 6. 保存图片 saveImages(record, notify); // 7. 推送第三方系统 pushToExternalSystem(record); weighRecordMapper.updateById(record); } }这里有个业务细节需要说清楚:毛重和皮重的区分,依赖的是车辆方向。在我这个系统里,车辆档案里保存了"常用皮重",如果车牌是内部常跑车辆,第一次上磅就能直接带出皮重,一次过磅完成;外部车辆则必须两次过磅。如果是"出厂"方向,那么第一次读到的是毛重,出厂后再读到的反而是皮重。所以每次读取重量时,不能只看当前值,还要看记录的方向和已有重量字段。
还有一个跨界问题:怎么判断"车辆是否完全上磅"?我最初只用重量值变化来判断,但现场有人把车停在磅边让重量轻微变化,系统就傻了。后来加了地感线圈信号,只有当重量超过一个触发阈值(比如500公斤)且地感信号为真时,才认为车辆上磅,这套逻辑才算稳定。这个部分不在源码里,但是在现场调试时必须带上。
5.4 与第三方ERP/MES对接的接口设计
称重数据最终要推送给工厂的ERP系统。这里涉及到接口协议选型。我见过太多项目,称重系统做完,对接ERP时因为接口字段不一致扯皮扯半天。所以设计时我就把推送接口独立出来,做成可配置的多通道推送。
我采用HTTP POST + JSON + 重签名的方案。每次推送称重记录时,携带的字段至少包括:record_no、plate_no、gross_weight、tare_weight、net_weight、weigh_time、direction。第三方系统如果出现短暂不可用,就用本地消息表落库,通过定时任务补推。等ERP恢复正常,可以自动把积压的记录推完。
6. 调试部署阶段的真实踩坑记录:给后来的项目省几个月时间
6.1 摄像头IP与网段冲突
这是我在项目开工第一天踩的坑。设备到场,海康相机默认IP是192.168.1.64,芊熠相机默认IP是192.168.1.120,仪表通过串口不过网。现场局域网网段恰好是192.168.1.x,但网关是192.168.1.1,整个局域网里已经有几十台设备。
结果我把海康相机插上网线,整个网络立刻瘫痪,广播风暴。因为海康出厂设置开了DHCP,而现场没有DHCP服务器,它自动分配了一个和网关冲突的IP。
解决方案:所有摄像头第一次上电前,必须用海康SADP工具或者芊熠配置工具,先把IP改成规划好的静态地址,并且禁止DHCP。顺序不能反。我在项目中专门写了一个工具类,通过ARP扫描局域网内设备,防止新设备IP撞车。
6.2 仪表串口通讯不稳定时怎么办
现场最头疼的是串口丢帧。我一开始以为串口波特率、校验位设置错了,查了半天,后来用串口监视器一看,PC发命令到仪表没问题,但仪表返回数据时总是断断续续。原因很简单:称重仪表放在磅房里,和电脑之间的串口线穿过强电电缆桥架,电磁干扰严重。
解决方法不复杂但很有效。第一,换成带屏蔽层的RS232线,外层屏蔽单端接地;第二,串口线尽量不走强电桥架,实在要走,穿金属管。第三,软件上,对于收到的每帧数据,做格式校验,不合法就丢弃重新等下一帧。这样处理过后,丢帧率从10%降到了几乎为零。
如果现场距离超过15米,RS232就不行了,建议改用RS485转USB,抗干扰能力完全不是一个级别。不过要注意,RS485是半双工的,需要控制发送和接收方向。
6.3 车牌识别误判和漏识别怎么兜底
芊熠车牌识别再牛,也有识别失败的时候。最常见的是泥浆遮挡车牌、强逆光、夜间车牌反光。我最开始直接依赖相机输出,结果现场运行三天,统计发现漏识别率大概在3%左右。这个比例对于无人值守系统来说太高了。
兜底方案我做了一整套:
- 第一层,芊熠识别结果回来后,做正则校验,车牌不符合标准格式就标记为"待人工确认"
- 第二层,同一辆车如果连续5次识别结果不一致,系统自动触发海康SDK抓拍,并导入人工审核队列
- 第三层,后台管理页面专门做一个"无车牌登记"入口,手机或PC端手动补录车牌,补录后自动和重量记录关联
最终这套系统即使识别失败,也不会导致过磅业务停摆,最多就是多一个人工确认步骤。
6.4 工地现场的防尘防雷与UPS断电保护
最后这点是写给做硬件部署的人看的。地磅房现场环境远比机房里恶劣,粉尘大、温差大、雷雨多。摄像头装在立杆上,必须有防雷器,否则一个雷击,网口和电源口都可能烧掉。我项目里第一次雷雨季后,四台相机烧了两台,后来统一加了防雷器才解决。
整个系统建议配一台小功率UPS,容量不用太大,能撑十分钟就行。因为突然断电很可能导致称重记录写到一半,仪表那边没影响,但数据库和图片文件可能损坏。我用的MySQL开了binlog,断电恢复后可以做一致性修复,但最好还是别断电。UPS还能给串口仪表供上电,防止仪表本身突然掉电导致内部标定参数丢失。
还有一点,地磅房防尘也关键。工业现场的灰尘会进串口接口、摄像头尾线,时间久了接触不良。所有室外接线接口要用防水胶带缠好,再套热缩管。这个细节施工队不会替你想,自己盯紧点。
最后再分享一个我自己的习惯:上线前无论如何要抽取连续三天的完整测试数据,对比人工记录的磅单和系统自动记录的差异。不要小看这一步,很多隐藏问题,比如某个时间段芊熠相机补光后识别率下降、某台海康相机SDK连接不稳定,都会在长时间运行测试里暴露出来。等真正投入生产后再发现,往往会付出几倍成本去补救。
本文还有配套的精品资源,点击获取