最近4D毫米波雷达的产品化应用越来越广泛,多家自动驾驶、辅助驾驶的产品开始量产装车4D Radar,由于它价格低廉(千元左右),探测距离达到200米+,相比激光雷达能穿透烟尘,可以覆盖大雾、雪天等场景,甚至可以探测到视觉和激光因遮挡看不到的车辆和障碍物,随着技术的进一步发展迭代,未来有代替激光雷达的趋势,因此本文将对4D毫米波进行初步的研究,看看点云的输出密度和准确度上跟激光雷达有多大差异。
一、概述
1. 4D Radar的技术特点
我们以一个“夜间+大雾”场景来说明4D毫米波的技术优势。
- 摄像头依赖光线,在夜里+大雾等能见度低的场景下,图像可能模糊、对比差、细节缺失;
- 激光雷达也可能受到雾、雨、雪、水滴、雪花、粉尘等散射、反射、吸收的影响,导致点云质量下降。
因此传统以视觉(摄像头)+激光雷达为主的感知方案,有其局限性。
相比之下,毫米波雷达的波长更长,对雨、雾、灰尘、水滴等的穿透性较强,不容易受到光照和能见度的影响。尤其是4D毫米波雷达,即便在雾、夜等低能见度条件下,仍能有效探测周围物体,并且具备了类似激光雷达的立体(3D+高度)感知能力,因此被视为一种“全天候/全时段”的感知传感器。
2. 4D Radar的局限性
2.1 点云稀疏&分辨率/细节有限
即便是4D毫米波雷达,其点云数量/密度/分辨率与高线束、高分辨率的激光雷达相比,仍然有一定差距。像是在多车并道、小车距离很近、障碍物形态复杂(栏杆、路缘、交通锥、行人、小型电动车…)等复杂场景,4D毫米波雷达可能只能检测到一个“点”或很少几个反射点,难以准确判断物体的形状、边界、尺寸、类别(是车、人、栏杆还是树?)。
2.2 点云“噪声+稀疏+不确定性”问题
雷达波反射可能因地面、湿度、水滴、雾滴、建筑反射、地面铺装、其他车辆金属结构、雨雪、环境杂散波干扰等产生噪声,而4D毫米波雷达点云本来就比激光雷达稀疏,当遇到复杂/静态/低反射目标(比如黑色物体、空气中轻微雾滴、透明物体…)时可能根本没有回波或回波太弱。这样的不确定性使得仅靠4D毫米波雷达判断路况显得不够可靠。
4D radar在低速场景可结合摄像头进行感知,在高速场景可辅助激光雷达探测遮挡障碍物、穿透雾、雪等场景。
3. SFR-2K技术参数
本文以赛恩领动SFR-2K为例,进行技术验证。赛恩领动的4D radar是一款车规级成像雷达,拥有120°水平视场角,最长探测距离300m,可以全天候全工况提供探测物体的距离、速度、角度以及高度等信息。
具体技术参数如下:
视觉遮挡区域的障碍物探测能力:
二、准备工作
1. 雷达连线
红黑线接12V供电,黄绿CAN线不用接,网络线这里通过转换盒子转为网口接PC。
2. 网络配置
SINPRO-SFR-2K出厂默认IP为192.168.3.10,我们这里把本机及网关配置到192.168.3网段:
网络配置完毕后可以ping通4d radar:
tcpdump抓包,可以看到4d radar在广播数据:
三、雷达调试
1. SFR-2K通信协议
赛恩领动官方没有提供driver和demo程序,只有通信报文协议定义。
SFR-2K的对外报文共有6种类型:1点云、2目标、3障碍物、4系统信息、7日志、8Dump,主要使用前4种。
传输协议使用UDP,报文头48bytes:
Payload根据报文类型区分,有不同的包大小和解析协议:
雷达报文发送周期15hz,默认发包顺序如下:(可能会丢包、乱序)
2. 驱动编写
我们这里根据协议定义编写driver,并将点云输出到topic,以用于可视化。
通信:接收radar的udp组播数据
bool RadarDriver::connect() {
int _socket_recv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_socket_recv < 0) {
std::cout << " [SOCKET] Init send socket failed" << std::endl;
return false;
}
int reuse_mode = 1;
if (setsockopt(_socket_recv, SOL_SOCKET, SO_REUSEPORT, &reuse_mode, sizeof(reuse_mode))) {
std::cout << " [SOCKET] set REUSEPORT failed." << std::endl;
close(_socket_recv);
return false;
}
sockaddr_in dst_addr; // radar information
memset(&dst_addr, 0, sizeof(dst_addr));
dst_addr.sin_family = AF_INET;
dst_addr.sin_addr.s_addr = inet_addr("192.168.3.10");
dst_addr.sin_port = htons(11011);
sockaddr_in local_addr; // driver information
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY; // inet_addr("192.168.3.100");
local_addr.sin_port = htons(11011);
// send
if (bind(_socket_recv, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
std::cout << " [SOCKET] bind local address error" << std::endl;
return false;
}
// add multicast group
struct ip_mreq multicast_group;
multicast_group.imr_multiaddr.s_addr = inet_addr("239.168.3.1"); // 组播ip
multicast_group.imr_interface.s_addr = htonl(INADDR_ANY); // inet_addr("192.168.3.100"); // 本机ip
if (setsockopt(_socket_recv, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicast_group, sizeof(multicast_group)) < 0) {
std::cout << " [SOCKET] Adding multicast group failed, error: " << strerror(errno) << std::endl;
close(_socket_recv);
return false;
}
std::cout << " [SOCKET] init recv socket successfully" << std::endl;
}
bool RadarDriver::recive() {
// 接收并打印数据
struct sockaddr_in multicastAddr;
char buffer[65536];
while (true)
{
socklen_t addrLen = sizeof(multicastAddr);
ssize_t recvLen = recvfrom(_socket_recv, buffer, sizeof(buffer), 0 , (struct sockaddr *)&multicastAddr, &addrLen);
if (recvLen < 0){
std::cout << " [SOCKET] recvfrom failed" << std::endl;
return false;
}
buffer[recvLen] = '\0';
std::cout << " [SOCKET] Receive multicast message: " << buffer << std::endl;
}
return true;
}
解析:参考协议解析udp包header,然后直接memcpy到对应结构体即可,这里不再赘述。
主程序:接收并发送点云topic
int main(int argc, char **argv) {
ros::init(argc, argv, "sinpro_sfr_2k-node");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<sensor_msgs::PointCloud2>("/sensor/sinpro_sfr_2k/points", 1);
// init radar udp multicast
std::shared_ptr<RadarDriver> _driver;
if (!_driver->init()) {
std::cout << " [INIT] RadarDriver Init Failed" << std::endl;
return;
}
std::cout << " [INIT] RadarDriver Init Success. Port: " << _driver->get_port() << std::endl;
// start recive data
while (ros::ok()) {
if (!_driver->receive()) {
std::cout << " not recive data!" << std::endl;
continue;
}
if (_driver->get_data()->pointcloud_size() > 0) {
std::cout << "[DATA] recive pointcloud size " << _driver->get_data()->pointcloud_size() << " recive object_num " << object_num << " recive obstacle_num " << obstacle_num << std::endl;
// 输出点云到topic
pcl::PointCloud<pcl::PointXYZ> cloud;
for (int i = 0; i < _driver->get_data()->pointcloud_size(); ++i) {
auto point = _driver->get_data()->pointcloud(i);
if (point.rng_m() < 0 || point.rng_m() > 300){
std::cout << " point rng_m out rang: " << point.rng_m() << std::endl;
continue;
}
// 4D毫米波径向距离、方位角、俯仰角,转换为ros点云x,y,z坐标
float x = point.rng_m() * cos(point.ele_rad()) * cos(point.azi_rad());
float y = point.rng_m() * cos(point.ele_rad()) * sin(point.azi_rad());
float z = point.rng_m() * sin(point.ele_rad());
cloud.points.push_back(pcl::PointXYZ(x, y, z));
}
sensor_msgs::PointCloud2 msg;
pcl::toROSMsg(cloud, msg);
msg.header.stamp = ros::Time::now();
msg.header.frame_id = "base_link";
pub.publish(msg);
}
}
_driver->close();
ros::AsyncSpinner spinner(1);
spinner.start();
ros::waitForShutdown();
}
3. 点云可视化
这里从俯视图可以看到点云密度还是很稀疏的,3D立体图上看点云更加稀疏,跟16线的激光雷达还是完全没法比的。
结论:作为主传感器探测信息不够,作为辅助传感器是可行的。
yan 25.12.11
参考:












