一、产品说明
本产品主要应用于 AGV 小车定位导航,通过 AGV 导航传感器采集图像,并通过内部算法处理,解析画面中的二维码所代表的。
产品特性:
- 内置读码算法,可高效读取 DM 码、码带和矩阵码
- 内置色带定位算法,可高效定位色带位置
- 算法鲁棒性强,可有效应对条码脏污、缺损、低对比度的情形
- 选用优秀的 Sensor,图像质量优异,图像数据高速采集
- 多指示灯用于调试和状态显示
- M12 低畸变镜头,支持大视野读码定位
- 优秀的光学设计,补光均匀,适应强反光场景


二、接口介绍与定义
设备接口为 12-pin P10 接口,提供供电、以太网和串口等功能,具体管脚信号定义如图 2-2、表 2-2 所示。



我使用的扫码器型号是MV-SC2005AM-02WBN AGV 导航读码器,他只能扫描地码,码带和色带是扫描不出来的。
三、相机的使用
本次实验就是使用扫码器识别地码,接线:(1)、用一根网线连接到电脑。(2)、接电源线,白色接24v,黑色接0v。
(1)、下载4.2.4版本的IDMVS软件
我第一次使用的版本是5.1版本的IDMVS软件,但是点击开始采集的时候会显示黑屏。就是相机识别不到图像。就像下边一样。

解决办法就是下载回4.2.4版本的IDMVS,但是有时后打开还是会黑屏,后面只需要重启一下设备就行了。

(2)、打开开始采集,就能看到图像了。

(3)、信息解读

右下角就的绿色部分就是扫描出来的关于码的信息了。
(3)、信息解读

<0007@(315.000x446.000)19.805@...@1-1-1>



这个就是相机的简单使用了,具体的参数调节可以看手册。

四、使用udp协议通讯接收相机的识别信息
(1)、IDMVS软件的设置
进入相机的设备树

(2)、找到传输控制


下面的这个就是数据传输的路径

(2)、编写py脚本接收并解析数据
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""海康 MV-SC2005AM-02WBN 地码读码器 UDP 接收测试。
配置相机(IDMVS -> Transport Layer Control):
Result Transfer Prototal = UDP
Smart Udp Enable = On
UDP Dst Addr = 192.168.20.55
UDP Dst Port = 1024
启动: python hikrobot_udp.py
结束: Ctrl+C
仅使用 Python 标准库。
说明:按照用户提供的《AGV导航读码器-客户版》UDP示例解析:
HKVS 头、命令码、128 字节 ASCII 识别结果区域;不同固件可能有差异。
物理坐标二进制字段暂不按固定偏移强行解码,保存原始包供核对。
"""
import argparse
import re
import socket
from datetime import datetime
from pathlib import Path
MAGIC = b"HKVS"
# 示例帧中:magic(4)+packet_id(4)+reserved(4)+command(4)+length(4)+timestamp(4)
# +algorithm_ms(4)+flags(4)+info_length(4) = 36 bytes,随后128字节文本区。
ASCII_OFFSET = 36
ASCII_SIZE = 128
PATTERN = re.compile(
r"<([^@<>]+)@\(\s*([+-]?\d+(?:\.\d+)?)\s*x\s*"
r"([+-]?\d+(?:\.\d+)?)\s*\)\s*([+-]?\d+(?:\.\d+)?)"
r"@([^@<>]*)@([^@<>]*)@([01]-[01]-[01])>"
)
def parse_packet(data: bytes):
"""返回 (metadata, result, ascii_text)。解析失败 result 为 None。"""
meta = {"size": len(data), "magic_ok": data.startswith(MAGIC)}
if len(data) < ASCII_OFFSET or not meta["magic_ok"]:
return meta, None, ""
meta.update(
packet_id=int.from_bytes(data[4:8], "big"),
command=int.from_bytes(data[12:16], "big"),
length_field=int.from_bytes(data[16:20], "big"),
algorithm_ms=int.from_bytes(data[24:28], "big"),
flags=list(data[28:31]),
)
raw_text = data[ASCII_OFFSET:ASCII_OFFSET + ASCII_SIZE].split(b"\x00", 1)[0]
ascii_text = raw_text.decode("ascii", errors="replace")
match = PATTERN.search(ascii_text)
if not match:
return meta, None, ascii_text
code, x, y, angle, cost, timestamp, flags = match.groups()
return meta, {
"code": code,
"x_px": float(x),
"y_px": float(y),
"angle_deg": float(angle),
"cost": cost,
"timestamp": timestamp,
"status": flags,
}, ascii_text
def main():
parser = argparse.ArgumentParser(description="海康 AGV 地码读码器 UDP 接收测试")
parser.add_argument("--bind", default="0.0.0.0", help="监听IP,默认0.0.0.0")
parser.add_argument("--port", type=int, default=1024, help="监听端口,默认1024")
parser.add_argument("--camera-ip", default="192.168.20.99", help="相机来源IP;仅做提示,仍打印其它来源")
parser.add_argument("--hex", action="store_true", help="每包打印完整十六进制")
parser.add_argument("--all", action="store_true", help="打印每个包,默认只打印识别变化和每隔50包状态")
parser.add_argument("--save", default="hikrobot_udp_packets.bin", help="保存原始包的文件名(长度前缀)")
args = parser.parse_args()
save_path = Path(args.save)
count = 0
last_state = None
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp, save_path.open("ab") as dump:
udp.bind((args.bind, args.port))
udp.settimeout(2)
print("=" * 60)
print("海康 SC2005AM UDP 通信测试(Ctrl+C退出)")
print(f"相机IP预期:{args.camera_ip}")
print(f"监听地址:{args.bind}:{args.port}")
print(f"原始报文保存:{save_path.resolve()}")
print("=" * 60)
try:
while True:
try:
data, (src_ip, src_port) = udp.recvfrom(65535)
except socket.timeout:
if count == 0:
print("尚未收到数据:请检查相机UDP使能、目的IP/端口、防火墙。")
continue
count += 1
dump.write(len(data).to_bytes(4, "big") + data)
dump.flush()
meta, result, text = parse_packet(data)
if args.hex:
print(f"\nHEX {src_ip}:{src_port}({len(data)}字节):\n{data.hex(' ').upper()}")
if result is not None:
state = (result["code"], result["x_px"], result["y_px"], result["angle_deg"], result["status"])
if args.all or state != last_state:
print(f"\n[{datetime.now():%H:%M:%S}] 来自 {src_ip}:{src_port},共收到 {count} 包")
print(f"地码编号: {result['code']}")
print(f"图像中心坐标: X={result['x_px']:.3f} px, Y={result['y_px']:.3f} px")
print(f"地码角度: {result['angle_deg']:.3f}°")
print(f"算法耗时: {result['cost']} ms | 时间戳: {result['timestamp']}")
print(f"状态(定位-译码-ROI): {result['status']}")
last_state = state
elif args.all or count == 1 or count % 50 == 0:
print(f"[{datetime.now():%H:%M:%S}] 第{count}包 来自 {src_ip}:{src_port} "
f"长度={len(data)},magic={meta['magic_ok']} "
f"命令={meta.get('command', '未知')},未匹配地码文本")
if text:
print(f"ASCII: {text[:180]!r}")
if not args.hex:
print("前64字节HEX:", data[:64].hex(" ").upper())
if src_ip != args.camera_ip and (args.all or count == 1):
print(f"提示:收到的来源IP {src_ip} 与预期相机IP {args.camera_ip} 不同。")
except KeyboardInterrupt:
print(f"\n退出,共收到 {count} 包。原始报文已保存:{save_path.resolve()}")
except PermissionError as exc:
print(f"保存报文权限不足:{exc}")
if __name__ == "__main__":
main()
(3)、脚本运行结果
情况1:未识别到地码
python
[16:13:55] 第5600包 来自 192.168.20.99:8192 长度=268,magic=True 命令=1,未匹配地码文本
前64字节HEX: 48 4B 56 53 00 00 A7 9C 00 00 00 00 00 00 00 01 00 00 00 F8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[16:13:56] 第5650包 来自 192.168.20.99:8192 长度=268,magic=True 命令=1,未匹配地码文本
前64字节HEX: 48 4B 56 53 00 00 A7 CE 00 00 00 00 00 00 00 01 00 00 00 F8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[16:13:56] 第5700包 来自 192.168.20.99:8192 长度=268,magic=True 命令=1,未匹配地码文本
前64字节HEX: 48 4B 56 53 00 00 A8 00 00 00 00 00 00 00 00 01 00 00 00 F8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
情况2:识别到地码
python
[16:15:02] 来自 192.168.20.99:8192,共收到 359 包
地码编号: 0007
图像中心坐标: X=386.000 px, Y=427.000 px
地码角度: 62.071°
算法耗时: 06 ms | 时间戳: 001500ac
状态(定位-译码-ROI): 1-1-1
[16:15:02] 来自 192.168.20.99:8192,共收到 360 包
地码编号: 0007
图像中心坐标: X=387.000 px, Y=427.000 px
地码角度: 62.093°
算法耗时: 06 ms | 时间戳: 001500b8
状态(定位-译码-ROI): 1-1-1
经过脚本测试,相机一上电就开始识别是否存在地码了(一上电就工作了),不需要去IDMVS软件上点击开始采集。