海康 AGV 导航读码器学习

一、产品说明

本产品主要应用于 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软件上点击开始采集。

相关推荐
qq_284274053 小时前
机械原理笔记:平面机构自由度计算(复合铰链、局部自由度、虚约束)与四杆机构入门(含考点)
笔记·学习·平面·自动化·制造
qq_284274054 小时前
数控铣床与加工中心笔记:铣刀与刀柄系统、G54试切对刀、镜像加工指令、孔加工工艺(钻扩铰镗)与固定循环G73/G83/G81/G76/G87/G84
笔记·学习·自动化·制造
Titan20245 小时前
MySQL访问个人学习笔记
笔记·学习·mysql
小雪崩6 小时前
嵌入式学习 day64:字符设备驱动
学习
个 人 练 习 生6 小时前
C++ string 类模拟实现:从底层理解字符串(上)
开发语言·c++·经验分享·学习·程序人生
坤坤子吖6 小时前
C++智能指针:RAII、shared_ptr与内存泄漏
开发语言·c++·笔记·学习
词却8 小时前
OpenCV学习:MediaPipe 人脸网格检测
opencv·学习
李日华大战鸡红8 小时前
FOC SVPWM过调制(学习记录)
stm32·单片机·学习
AI职业加油站8 小时前
AI 校招现状:大模型应用工程师证书,助力简历能力证明
大数据·运维·人工智能·学习·职场发展