Files
Loongson_2k0300_SmartCar/src/camera.cpp
T

1324 lines
61 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* camera.cpp — 每帧感知-决策-执行流水线
*
* 入口: CameraHandler() 由 main.cpp 主循环每帧调用一次。
*
* 10 步流水线:
* 1. capture_frame MJPEG 取帧 → 1/4 解码 160×120 BGR
* 2. save_image_if_requested debug 存图
* 3. image_main 视觉巡线 → left/right/mid_line[]
* 4. lidar_avoid_process VL53L0X 激光避障 → 中线变形 (已禁用)
* 5. zebra/tl/cone 场景识别状态机 (用上一帧 g_boxes[])
* 6. steering_update 舵机比例控制
* 7. motor_update 电机开环 + 编码器刹车 + 弯道减速
* 8. run_model_inference Mild v12 每2帧推理 → g_boxes[] (放最后, 不阻塞舵机)
* 9. lcd_render LCD RGB565 渲染
* 10. fps_log 分步耗时日志
*
* 硬件依赖:
* - 摄像头: /dev/video0 (640×480 MJPEG, V4L2)
* - LCD: /dev/fb0 (RGB565, mmap)
* - 舵机: pwmchip1/pwm0 (3ms 周期, 1.2~1.8ms 占空比)
* - 语音: /dev/i2c-2 从地址 0x34 (斑马线播报)
* - 激光: VL53L0X via /dev/stmvl53l0x_ranging
*/
#include "camera.h"
#include "model_v10.hpp"
#include "vl53l0x.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <linux/fb.h>
#include <algorithm>
#include <chrono>
#include <thread>
#include <cstring>
#include <sstream>
#include <iomanip>
#include <malloc.h>
// ═══════════════════════════════════════════════════════════
// 模型检测类别索引
// Mild v12 输出 4 类 + 背景, 对应 g_thresh[] 阈值
// ═══════════════════════════════════════════════════════════
enum { MD_CONE = 0, MD_RED = 1, MD_GREEN = 2, MD_ZEBRA = 3 };
// ═══════════════════════════════════════════════════════════
// 摄像头 & 帧缓冲全局变量
// ═══════════════════════════════════════════════════════════
cv::VideoCapture cap; // V4L2 摄像头句柄
cv::Mat raw_mat, decoded_frame; // raw_mat=V4L2原始帧, decoded_frame=MJPEG解码结果
static int g_decode_mode = -1; // -1=未检测, 1=MJPEG 1/4解码, 0=全尺寸回退
int screenWidth, screenHeight; // LCD 屏幕物理分辨率 (从 /dev/fb0 读取)
int newWidth, newHeight; // 自适应显示分辨率 = 摄像头分辨率 × 缩放因子
int fb; // /dev/fb0 文件描述符
uint16_t *fb_buffer; // LCD 帧缓冲 mmap 地址
static size_t g_fb_size = 0; // 帧缓冲字节数
static cv::Mat g_stop_image; // 斑马线停车提示图片
PwmController servo(1, 0); // 舵机: pwmchip1/pwm0
// 巡线空间到显示空间的缩放因子
// line_tracking_width = newWidth / calc_scale
// line_tracking_height = newHeight / calc_scale
static constexpr int calc_scale = 2;
// ═══════════════════════════════════════════════════════════
// 模型推理相关
//
// g_thresh: 四类各自置信度阈值 (锥桶/红灯/绿灯/斑马线)
// g_boxes: 检测结果缓冲区 (最多 16 个框)
// g_box_count: 当前帧有效检测框数
// ═══════════════════════════════════════════════════════════
static float g_thresh[4] = {0.80f, 0.70f, 0.80f, 0.85f};
static DetectBoxV10 g_boxes[16];
static int g_box_count = 0;
// ═══════════════════════════════════════════════════════════
// 斑马线状态机
//
// 状态: Z_NORMAL → Z_STOP(4s) → Z_COOLDOWN(5s) → Z_NORMAL
// 触发条件: 连续 5 帧 + 起源于远(cy≤50) + 当前近(cy>zebrasee)
//
// g_zc_frames: 去抖连续帧计数 (未检测时每帧 -2 衰减)
// g_zc_min_cy: 本轮追踪中斑马线出现时的最小 cy (越小=越远)
// g_ztime: 进入 Z_STOP / Z_COOLDOWN 的时间戳
// ═══════════════════════════════════════════════════════════
static constexpr int ZEBRA_MIN_FRAMES = 3; // 连续确认帧数
static constexpr int ZEBRA_FAR_CY = 80; // 远处 cy 阈值: 斑马线必须曾出现在 cy≤80
static int g_zc_frames = 0;
static int g_zc_min_cy = 120; // 初始设远 (120=从未检测到)
enum ZState { Z_NORMAL, Z_STOP, Z_COOLDOWN };
static ZState g_zstate = Z_NORMAL;
static time_t g_ztime = 0;
static bool g_bangbang_used = false; // 本轮 Z_COOLDOWN 打满机会是否已用
static bool g_bangbang_active = false; // 是否处于打满->等回正序列中
static int g_bangbang_dir = 0; // 锁定方向: +1=右满, -1=左满
// ═══════════════════════════════════════════════════════════
// 红绿灯状态机
//
// 状态: TL_NORMAL → TL_STOP → TL_WAIT_GREEN → TL_NORMAL
// 红灯≥3帧确认停车, 红灯消失→等绿灯, 绿灯≥3帧确认通行
//
// g_tl_red_frames: 红灯连续帧计数 (未检测时 -1 衰减)
// g_tl_green_frames: 绿灯连续帧计数
// ═══════════════════════════════════════════════════════════
enum TLState { TL_NORMAL, TL_STOP, TL_WAIT_GREEN };
static TLState g_tl_state = TL_NORMAL;
static int g_tl_red_frames = 0;
static int g_tl_green_frames = 0;
// ═══════════════════════════════════════════════════════════
// 锥桶检测 & 中线变形状态
//
// 生命周期: 确认(avoid) → 保持衰减(hold) → 回弹(return) → 空闲
//
// g_cone_frames: 去抖连续帧计数 (未检测时 -1 衰减)
// g_cone_last_row/col: 上帧锥桶位置 (用于位置容差判定)
// g_cone_confirmed: 是否通过去抖确认
// g_cone_hold_ctr: 保持衰减计数器 (确认时=1, 每帧++)
// g_cone_hold_src_row/col: 保持阶段使用的锥桶位置 (确认时冻结)
// g_cone_return_ctr: 回弹计数器 (保持结束后=1, 每帧++)
// g_cone_return_dir: 回弹方向 (+1=推右, -1=推左, 与避让方向相反)
// ═══════════════════════════════════════════════════════════
static int g_cone_frames = 0;
static int g_cone_last_row = -1;
static int g_cone_last_col = -1;
static bool g_cone_confirmed = false;
static int g_cone_hold_ctr = 0;
static int g_cone_hold_src_row = -1;
static int g_cone_hold_src_col = -1;
static int g_cone_return_ctr = 0;
static double g_cone_return_dir = 0;
// ═══════════════════════════════════════════════════════════
// 激光雷达挡板避障
//
// 每 2 帧非阻塞读取 VL53L0X 距离, 纯距离触发(不检查视觉边线)。
// 绕行方向固定朝右 (g_lidar_hold_dir = 1.0)。
//
// g_lidar_sensor: VL53L0X 驱动实例
// g_lidar_ok: 硬件是否初始化成功
// g_lidar_frames: 去抖连续帧计数
// g_lidar_confirmed: 是否通过去抖确认
// g_lidar_obstacle_row: 障碍物对应的巡线空间行号
// g_lidar_hold_src_row: 保持阶段使用的障碍物行号 (确认时冻结)
// g_lidar_hold_ctr: 保持衰减计数器
// g_lidar_hold_dir: 绕行方向 (固定 1.0 = 朝右)
// ═══════════════════════════════════════════════════════════
static VL53L0X g_lidar_sensor;
static bool g_lidar_ok = false;
static int g_lidar_frames = 0;
static bool g_lidar_confirmed = false;
static int g_lidar_obstacle_row = -1;
static int g_lidar_hold_src_row = -1;
static int g_lidar_hold_ctr = 0;
static double g_lidar_hold_dir = 0;
// ═══════════════════════════════════════════════════════════
// LCD 开关 & 舵机偏差
// ═══════════════════════════════════════════════════════════
static bool g_lcd_on = true; // LCD 渲染开关 (每 10 帧从 g_cfg.showImg 刷新)
double g_steer_deviation = 0; // 归一化舵机偏差 [-1, 1], 供 motor_update 弯道减速用
// ═══════════════════════════════════════════════════════════
// I2C 音频 — 斑马线语音播报
//
// 硬件: I2C-2 总线, 从地址 0x34, SMBus 块写
// 协议: command=0x6E, data={0xFF, 0x10} → 触发语音
// 调用时机: 斑马线状态机进入 Z_STOP 时
// ═══════════════════════════════════════════════════════════
static int i2c_audio_fd = -1;
// 打开 I2C 音频设备 (懒初始化, 只打开一次)
static bool i2c_audio_open()
{
if (i2c_audio_fd >= 0) return true; // 已打开
i2c_audio_fd = open("/dev/i2c-2", O_RDWR);
if (i2c_audio_fd < 0) {
fprintf(stderr, "[ZEBRA] 无法打开 I2C-2: %s\n", strerror(errno));
return false;
}
if (ioctl(i2c_audio_fd, I2C_SLAVE, 0x34) < 0) {
fprintf(stderr, "[ZEBRA] 无法设置 I2C 从地址 0x34: %s\n", strerror(errno));
close(i2c_audio_fd); i2c_audio_fd = -1;
return false;
}
return true;
}
// 触发斑马线语音播报: SMBus I2C 块写 → 音频模块
static void play_zebra_audio()
{
if (!i2c_audio_open()) { printf("[ZEBRA] 语音失败: I2C 未打开\n"); return; }
ioctl(i2c_audio_fd, I2C_SLAVE, 0x34); // 确保从地址
// SMBus I2C 块写: command=0x6E, data=[0xFF, 0x10]
union i2c_smbus_data d;
struct i2c_smbus_ioctl_data a;
__u8 v[] = {0xFF, 0x10};
d.block[0] = 2; d.block[1] = v[0]; d.block[2] = v[1];
a.read_write = I2C_SMBUS_WRITE;
a.command = 0x6E;
a.size = I2C_SMBUS_I2C_BLOCK_DATA;
a.data = &d;
if (ioctl(i2c_audio_fd, I2C_SMBUS, &a) < 0)
printf("[ZEBRA] 语音失败: %s\n", strerror(errno));
else
printf("[ZEBRA] 语音播报已触发\n");
}
// ═══════════════════════════════════════════════════════════
// CameraInit — 系统初始化
//
// 初始化顺序:
// 1. 舵机 → 中位
// 2. LCD 帧缓冲 → mmap
// 3. 摄像头 → 640×480 MJPEG, CONVERT_RGB=0
// 4. 自适应分辨率计算 → newWidth/newHeight, line_tracking_*
// 5. Mild v12 模型加载
// 6. I2C 音频设备
// 7. VL53L0X 激光 (可选, lidar_enable 控制)
// ═══════════════════════════════════════════════════════════
int CameraInit(int camera_id)
{
// ── 1. 舵机初始化: 3ms 周期, 1.5ms 中位 ──
servo.setPeriod(3000000); // 3,000,000 ns = 3ms
servo.setDutyCycle(1500000); // 1,500,000 ns = 1.5ms (中位)
servo.enable();
// ── 2. LCD 帧缓冲初始化 ──
fb = open("/dev/fb0", O_RDWR);
if (fb == -1) { std::cerr << "无法打开帧缓冲区设备" << std::endl; return -1; }
struct fb_var_screeninfo vinfo;
if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == -1) {
std::cerr << "无法获取帧缓冲区信息" << std::endl; close(fb); return -1;
}
screenWidth = vinfo.xres; screenHeight = vinfo.yres;
g_fb_size = vinfo.yres_virtual * vinfo.xres_virtual * vinfo.bits_per_pixel / 8;
fb_buffer = (uint16_t *)mmap(NULL, g_fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
if (fb_buffer == MAP_FAILED) {
std::cerr << "无法映射帧缓冲区到内存" << std::endl; close(fb); return -1;
}
// 加载斑马线停车提示图片(与 LCD 同分辨率 160x128
g_stop_image = cv::imread("./pedestrian_stop.png");
if (!g_stop_image.empty())
printf("[LCD] 已加载停车提示图片: %dx%d\n", g_stop_image.cols, g_stop_image.rows);
else
printf("[LCD] 警告: 未找到 ./pedestrian_stop.png\n");
// ── 3. 摄像头初始化 ──
// CONVERT_RGB=0: 让 V4L2 返回原始 MJPEG 字节流 (单通道)
// 后续用 imdecode(IMREAD_REDUCED_COLOR_4) 做 1/4 解码 → 160×120
cap.open(0, cv::CAP_V4L2);
if (!cap.isOpened()) cap.open(0); // V4L2 失败则尝试默认后端
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
cap.set(cv::CAP_PROP_CONVERT_RGB, 0); // 关键: 不让 V4L2 转 RGB
if (!cap.isOpened()) {
printf("无法打开摄像头\n");
munmap(fb_buffer, g_fb_size); close(fb); return -1;
}
int camW = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int camH = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
printf("摄像头分辨率: %d x %d\n", camW, camH);
// ── 4. 自适应分辨率: 在 LCD 屏幕上等比缩放 ──
// newWidth × newHeight = 摄像头画面等比放入 LCD 后的像素尺寸
double ws = (double)screenWidth / camW;
double hs = (double)screenHeight / camH;
double s = std::min(ws, hs); // 取小边缩放因子, 保证不超出屏幕
newWidth = (int)(camW * s);
newHeight = (int)(camH * s);
printf("自适应分辨率: %d x %d\n", newWidth, newHeight);
double fps = cap.get(cv::CAP_PROP_FPS);
printf("Camera fps: %f\n", fps);
// 巡线空间 = 显示空间 / calc_scale (典型 80×60)
line_tracking_width = newWidth / calc_scale;
line_tracking_height = newHeight / calc_scale;
// ── 5. 加载 Mild v12 模型 ──
if (!model_v10_init("./mild_v12.bin"))
printf("[MODEL] 警告: 模型加载失败\n");
else
printf("[MODEL] Mild v12 模型已加载\n");
// ── 6. I2C 音频设备 ──
i2c_audio_open();
// ── 7. VL53L0X 激光测距 (可选) ──
// 仅在 lidar_enable=1 时初始化, 避免驱动内核定时器拖慢 CPU
// if (g_cfg.lidar_enable) {
// g_lidar_ok = g_lidar_sensor.init();
// if (g_lidar_ok) {
// printf("[LIDAR] VL53L0X 已初始化\n");
// g_lidar_sensor.startMeasure(); // 启动首次后台测量 (~20ms)
// } else
// printf("[LIDAR] VL53L0X 未连接, 避障禁用\n");
// } else {
// printf("[LIDAR] 已禁用\n");
// }
return 0;
}
// 前置声明: LCD 辅助函数定义在后方
static void lcd_clear_screen();
// ═══════════════════════════════════════════════════════════
// cameraDeInit — 释放所有资源
// ═══════════════════════════════════════════════════════════
void cameraDeInit(void)
{
cap.release(); // 释放摄像头
lcd_clear_screen(); // 程序退出前清屏
if (fb_buffer && g_fb_size > 0) {
munmap(fb_buffer, g_fb_size); // 解除 LCD mmap
}
close(fb); // 关闭 LCD fd
if (i2c_audio_fd >= 0) close(i2c_audio_fd); // 关闭 I2C 音频
if (g_lidar_ok) g_lidar_sensor.stop(); // 停止 VL53L0X
model_v10_deinit(); // 释放模型内存
}
// ═══════════════════════════════════════════════════════════
// Step 2: 图像保存 (debug 模式)
//
// 条件: debug=1 且 ./saveImg 文件内容为 1
// 保存路径: ./image/image_XXXXX.jpg (自增编号)
// ═══════════════════════════════════════════════════════════
static int saved_frame_count = 0;
static void save_image_if_requested()
{
if (!g_cfg.debug) return;
if (!readFlag(saveImg_file)) return; // 每帧读 ./saveImg 文件
std::ostringstream filename;
filename << "./image/image_" << std::setw(5) << std::setfill('0') << saved_frame_count << ".jpg";
if (cv::imwrite(filename.str(), raw_frame)) {
printf("图像%d已保存\n", saved_frame_count);
saved_frame_count++;
}
}
// ═══════════════════════════════════════════════════════════
// Step 1: 帧捕获 + MJPEG 解码
//
// 正常模式 (CONVERT_RGB=0 生效):
// V4L2 返回单通道 MJPEG → imdecode(IMREAD_REDUCED_COLOR_4)
// → 1/4 解码 → raw_frame = 160×120 BGR
//
// 回退模式 (CONVERT_RGB=0 未生效):
// V4L2 返回三通道 BGR → 直用 → raw_frame = 640×480 BGR
//
// 首帧自动检测: raw_mat.channels()==1 → 正常模式, 否则回退
// ═══════════════════════════════════════════════════════════
static int capture_frame(struct timespec *t0, struct timespec *t1)
{
clock_gettime(CLOCK_MONOTONIC, t0); // 计时: 取帧开始
cap.read(raw_mat);
clock_gettime(CLOCK_MONOTONIC, t1); // 计时: 取帧结束
if (raw_mat.empty()) return -1;
// 首帧自动检测解码模式
if (g_decode_mode == -1) {
g_decode_mode = (raw_mat.channels() == 1) ? 1 : 0;
printf("[CAM] CONVERT_RGB=0 %s, mode=%d\n",
g_decode_mode == 1 ? "生效->1/4解码" : "未生效->全解码回退",
g_decode_mode);
}
if (g_decode_mode == 1) {
// 正常: MJPEG 字节流 → 1/4 解码 → 160×120 BGR
cv::imdecode(raw_mat, cv::IMREAD_REDUCED_COLOR_4, &decoded_frame);
if (decoded_frame.empty()) return -1;
raw_frame = decoded_frame;
} else {
// 回退: 直接使用 640×480 BGR (性能较差)
raw_frame = raw_mat;
}
return 0;
}
// ═══════════════════════════════════════════════════════════
// Step 5: 模型推理 (每 2 帧一次)
//
// 输入: raw_frame (160×120 BGR)
// 输出: g_boxes[0..g_box_count-1], 每个框含 cx,cy,w,h,cls,conf
// 阈值: g_thresh = {0.80, 0.75, 0.80, 0.87} (锥桶/红灯/绿灯/斑马线)
// 跳帧: 每 2 帧推理一次, 奇数帧保留上次结果
// ═══════════════════════════════════════════════════════════
static void run_model_inference()
{
static int infer_skip = 0;
if (++infer_skip < 2) return; // 跳帧
infer_skip = 0;
g_box_count = 0;
if (model_v10_ready()) {
g_box_count = model_v10_detect(raw_frame.data, raw_frame.cols, raw_frame.rows,
g_boxes, 16, g_thresh);
}
}
// ═══════════════════════════════════════════════════════════
// Step 6a: 斑马线检测 + 状态机
//
// 检测逻辑:
// 遍历 g_boxes 找 cls=3 (斑马线), 只取第一个 (break)
// 记录 cy, 更新 g_zc_min_cy (本轮最远出现位置)
// 未检测到时 g_zc_frames 每帧 -2 衰减, 归零则重置 min_cy
//
// 触发条件 (仅 Z_NORMAL 时):
// 1. zebra_near: 当前 cy > g_cfg.zebrasee (足够近)
// 2. g_zc_frames >= 5 (连续确认)
// 3. g_zc_min_cy <= 50 (曾从远处检测到, 排除误触发)
// → 播报语音 → Z_STOP
//
// 状态转移:
// Z_NORMAL → Z_STOP (停车 4 秒) → Z_COOLDOWN (冷却 5 秒) → Z_NORMAL
// 冷却期间不检测斑马线, 防止重复触发
//
// 返回值: true = Z_STOP (通知 motor_update 刹车)
// ═══════════════════════════════════════════════════════════
static bool zebra_process()
{
// 双边丢线超过50% → 不采纳任何斑马线检测
if (g_lost_rows > line_tracking_height / 2) {
if (g_cfg.debug) printf("[ZEBRA] 忽略: 丢线%d/%d行\n", g_lost_rows, line_tracking_height);
return false;
}
bool zebra_seen = false;
bool zebra_near = false;
int zebra_cy = 0;
float zebra_cf = 0;
// 遍历检测框, 找第一个斑马线
for (int i = 0; i < g_box_count; ++i) {
if (g_boxes[i].cls != MD_ZEBRA) continue;
// ── 赛道内占比检查: 框80%以上在赛道外 → 忽略 ──
{
float bx = (float)line_tracking_width / raw_frame.cols;
float by = (float)line_tracking_height / raw_frame.rows;
int bx1 = (int)((g_boxes[i].cx - g_boxes[i].w/2) * bx);
int by1 = (int)((g_boxes[i].cy - g_boxes[i].h/2) * by);
int bx2 = (int)((g_boxes[i].cx + g_boxes[i].w/2) * bx);
int by2 = (int)((g_boxes[i].cy + g_boxes[i].h/2) * by);
int overlap = 0, area = (bx2 - bx1) * (by2 - by1);
if (area > 0) {
int y0 = std::max(0, std::min(by1, line_tracking_height - 1));
int y1 = std::min(by2, line_tracking_height - 1);
for (int y = y0; y <= y1 && y >= 0; ++y) {
if (left_line[y] == -1 || right_line[y] == -1) continue;
int ol = std::max(0, std::min(bx2, right_line[y]) - std::max(bx1, left_line[y]));
overlap += ol;
}
}
if (area > 0 && (float)overlap / area < 0.2f) {
if (g_cfg.debug) printf("[ZEBRA] 忽略: 赛道内仅%.0f%% cf=%.2f\n",
(float)overlap / area * 100, g_boxes[i].conf);
continue; // 赛道内<20% → 忽略
}
}
zebra_cy = (int)g_boxes[i].cy;
zebra_cf = g_boxes[i].conf;
zebra_seen = true;
zebra_near = (zebra_cy > g_cfg.zebrasee); // cy > 阈值 = 足够近
if (g_zstate == Z_NORMAL) {
if (zebra_cy < g_zc_min_cy) g_zc_min_cy = zebra_cy; // 追踪最远 cy
g_zc_frames++;
}
break; // 只取第一个斑马线框
}
// 未检测到 → 衰减去抖计数
if (!zebra_seen) {
if (g_zc_frames > 0) g_zc_frames = std::max(0, g_zc_frames - 1); // 每帧 -1 衰减
if (g_zc_frames == 0) g_zc_min_cy = 120; // 完全衰减 → 重置远处追踪
}
time_t now = time(nullptr);
switch (g_zstate) {
case Z_NORMAL:
// 触发: 近 + 连续帧够 + 曾从远处出现
if (zebra_near && g_zc_frames >= ZEBRA_MIN_FRAMES && g_zc_min_cy <= ZEBRA_FAR_CY) {
play_zebra_audio(); // I2C 语音播报
g_zstate = Z_STOP; g_ztime = now;
if (g_cfg.debug) printf("[ZEBRA] cy=%d cf=%.2f 停车3s 冷却5s\n", zebra_cy, zebra_cf);
g_zc_frames = 0; g_zc_min_cy = 120; // 重置, 为下次检测做准备
} else if (zebra_seen && g_cfg.debug) {
if (!zebra_near)
printf("[ZEBRA] 忽略: 太远 cy=%d <= %d\n", zebra_cy, (int)g_cfg.zebrasee);
else if (g_zc_frames < ZEBRA_MIN_FRAMES)
printf("[ZEBRA] 忽略: 帧数不足 %d/%d cy=%d\n", g_zc_frames, ZEBRA_MIN_FRAMES, zebra_cy);
else if (g_zc_min_cy > ZEBRA_FAR_CY)
printf("[ZEBRA] 忽略: 未曾远距出现 min_cy=%d > %d\n", g_zc_min_cy, ZEBRA_FAR_CY);
}
break;
case Z_STOP:
if (now - g_ztime >= 3) { // 停车 3 秒
g_zstate = Z_COOLDOWN; g_ztime = now;
if (g_cfg.debug) printf("[ZEBRA] 起步\n");
}
break;
case Z_COOLDOWN:
if (now - g_ztime >= 5) { // 冷却 5 秒
g_zstate = Z_NORMAL;
if (g_cfg.debug) printf("[ZEBRA] 恢复\n");
}
break;
}
return (g_zstate == Z_STOP);
}
// ═══════════════════════════════════════════════════════════
// Step 6b: 红绿灯状态机
//
// 去抖: 红灯/绿灯各自独立计数, 连续 3 帧确认, 未检测时 -1 衰减
//
// 状态转移:
// TL_NORMAL → TL_STOP (红灯连续 ≥3 帧)
// TL_STOP → TL_WAIT_GREEN (红灯消失)
// TL_WAIT_GREEN → TL_NORMAL (绿灯连续 ≥3 帧)
//
// 返回值: true = 非 TL_NORMAL (通知 motor_update 刹车)
// ═══════════════════════════════════════════════════════════
static bool traffic_light_process()
{
// 遍历检测框, 统计红/绿灯是否出现
bool red_seen = false, green_seen = false;
for (int i = 0; i < g_box_count; ++i) {
if (g_boxes[i].cls == MD_RED) red_seen = true;
if (g_boxes[i].cls == MD_GREEN) green_seen = true;
}
// 去抖: 出现 +1, 消失 -1
if (red_seen) g_tl_red_frames++;
else g_tl_red_frames = std::max(0, g_tl_red_frames - 1);
if (green_seen) g_tl_green_frames++;
else g_tl_green_frames = std::max(0, g_tl_green_frames - 1);
switch (g_tl_state) {
case TL_NORMAL:
if (g_tl_red_frames >= 3) { // 红灯确认 → 停车
g_tl_state = TL_STOP;
if (g_cfg.debug) printf("[TL] 红灯停车\n");
}
break;
case TL_STOP:
if (!red_seen) { // 红灯消失 → 等绿灯
g_tl_state = TL_WAIT_GREEN;
if (g_cfg.debug) printf("[TL] 等待绿灯\n");
}
break;
case TL_WAIT_GREEN:
if (g_tl_green_frames >= 3) { // 绿灯确认 → 通行
g_tl_state = TL_NORMAL;
g_tl_red_frames = g_tl_green_frames = 0; // 清零, 重新开始
if (g_cfg.debug) printf("[TL] 绿灯通行\n");
}
break;
}
return (g_tl_state != TL_NORMAL);
}
// ═══════════════════════════════════════════════════════════
// Step 4: 激光雷达挡板避障
//
// lidar_is_active(): 判断是否处于确认或保持衰减状态
//
// lidar_avoid_process() 流程:
// 1. 每 2 帧非阻塞读取 VL53L0X (readResult + startMeasure)
// 2. 预触发去抖: d_mm >= lidar_pre → 帧数衰减; d_mm == 0 → 跳过
// 3. 距离→行映射: 线性映射 [lidar_near, lidar_far] → [lt_h-1, 10]
// 4. 触发判定: d_mm < lidar_thresh && frames >= min_frames → 确认
// 5. 保持衰减: 确认后 hold_ctr 递增, 超过 hold_frames 则失效
// 6. 中线变形: 固定朝右推, 斜坡从 row=10 上升到障碍行, 衰减渐弱
// ═══════════════════════════════════════════════════════════
static bool lidar_is_active() {
return g_lidar_confirmed ||
(g_lidar_hold_ctr > 0 && g_lidar_hold_ctr <= g_cfg.lidar_hold_frames);
}
static void lidar_avoid_process()
{
// ── 跳帧 + 前置检查 ──
static int lidar_skip = 0;
if (!g_lidar_ok || !g_cfg.lidar_enable) return; // 硬件未就绪或已禁用
if (++lidar_skip < 2) return; // 每 2 帧一次 (~15Hz)
lidar_skip = 0;
// ── 非阻塞读取上次测量结果, 立即启动下次测量 ──
VL53L0X_RangingMeasurementData_t data;
if (!g_lidar_sensor.readResult(data)) return; // 读取失败 → 跳过
int d_mm = data.RangeMilliMeter;
g_lidar_sensor.startMeasure(); // 后台测量 ~20ms
// ── 预触发去抖窗口 ──
int lt_h = line_tracking_height;
bool skip = false;
if (d_mm == 0) {
skip = true; // 传感器读数为 0 = 读失败
} else if (d_mm >= g_cfg.lidar_pre) {
if (g_lidar_frames > 0) g_lidar_frames--; // 超出窗口 → 衰减 (不归零)
skip = true;
}
if (!skip) {
// ── 距离 → 图像行映射 (线性) ──
// lidar_near(50mm) → row=lt_h-1 (最近), lidar_far(1200mm) → row=10 (最远)
int row = lt_h - 1 - (d_mm - g_cfg.lidar_near) * (lt_h - 11)
/ (g_cfg.lidar_far - g_cfg.lidar_near);
if (row < 10) row = 10;
if (row >= lt_h) row = lt_h - 1;
// ── 纯距离触发, 不检查视觉边界线 ──
g_lidar_frames++;
g_lidar_obstacle_row = row;
g_lidar_confirmed = (d_mm < g_cfg.lidar_thresh) && (g_lidar_frames >= g_cfg.lidar_min_frames);
static int dbg = 0;
if (g_cfg.debug && ++dbg >= 30) { dbg = 0;
printf("[LIDAR] d=%d row=%d frames=%d confirm=%d\n",
d_mm, row, g_lidar_frames, g_lidar_confirmed);
}
// ── 确认 → 记录保持状态 ──
if (g_lidar_confirmed) {
g_lidar_hold_ctr = 0;
g_lidar_hold_src_row = g_lidar_obstacle_row;
g_lidar_hold_dir = 1.0; // 固定朝右绕行
if (g_cfg.debug) printf("[LIDAR] 触发 d=%d row=%d dir=右\n", d_mm, g_lidar_obstacle_row);
g_lidar_frames = 0; // 重置计数
}
}
// ── 保持计数器递增 (确认中和消失后都递增) ──
hold_decay:
if (g_lidar_hold_src_row > 0 && g_lidar_hold_ctr <= g_cfg.lidar_hold_frames)
g_lidar_hold_ctr++;
if (!lidar_is_active()) return; // 非活跃 → 不变形
// ── 中线变形: 固定朝右推 ──
// 从 row=10 到障碍行, 斜坡线性上升; 障碍行以下维持最大推力
// 消失后 decay 线性衰减到 0
int src_row = g_lidar_hold_src_row;
double rng = (double)g_cfg.lidar_avoid_range; // 斜坡陡峭度
double half_w = line_tracking_width / 2.0;
double dir = g_lidar_hold_dir; // 1.0 = 朝右
double decay = g_lidar_confirmed ? 1.0 :
(1.0 - (double)g_lidar_hold_ctr / g_cfg.lidar_hold_frames);
for (int i = 10; i < lt_h; ++i) {
// 障碍行及以上: 斜坡上升; 障碍行以下: 满推力
double t = (i <= src_row) ? std::clamp((i - 10) / rng, 0.0, 1.0) : 1.0;
double push = t * g_cfg.lidar_avoid_gain * half_w * dir * decay;
double nm = std::clamp(mid_line[i] + push,
(double)left_line[i] + 2.0,
(double)right_line[i] - 2.0);
mid_line[i] = (int)(nm + 0.5);
}
}
// ═══════════════════════════════════════════════════════════
// Step 6c: 锥桶检测 + 中线变形
//
// cone_is_slow(): 判断是否处于避让/保持/回弹任一阶段
// (供 motor_update 减速用, 但减速代码当前已注释)
//
// cone_detect_and_deform() 流程:
// 1. 仅在 Z_NORMAL && TL_NORMAL 时运行
// 2. 遍历 g_boxes 找 cls=0 (锥桶), 置信度 ≥ cone_thresh
// 3. 模型坐标→巡线空间, 边界过滤 (row 有效、赛道内)
// 4. cone_margin: 0=用中心点判断, 1=用框左右边缘判断
// 5. 取 cy 最大(最近)的锥桶
// 6. 去抖: 位置容差内连续帧计数, 突变则重计
// 7. 确认 → 保持衰减 → 回弹, 三阶段生命周期
// 8. 避让阶段: 推离锥桶方向的中线变形 (row 10 ~ 锥桶行)
// 9. 回弹阶段: 朝锥桶方向反推 (帮助车回到中央)
// ═══════════════════════════════════════════════════════════
static bool cone_is_slow() {
return g_cone_confirmed ||
(g_cone_hold_ctr > 0 && g_cone_hold_ctr <= g_cfg.cone_hold_frames);
// (g_cone_return_ctr > 0 && g_cone_return_ctr <= g_cfg.cone_return_frames);
}
static void cone_detect_and_deform()
{
// 斑马线停车或红绿灯非正常时, 不做锥桶检测
if (g_zstate == Z_STOP || g_tl_state != TL_NORMAL) return;
// ── 1. 找最近锥桶 (cy 最大 = 最近) ──
bool cone_seen = false;
int cone_row = -1, cone_col = -1;
for (int i = 0; i < g_box_count; ++i) {
if (g_boxes[i].cls != MD_CONE) continue;
if (g_boxes[i].conf < g_cfg.cone_thresh) continue;
// 模型空间 → 巡线空间坐标映射
int cl = g_boxes[i].cx * line_tracking_width / raw_frame.cols;
int rl = g_boxes[i].cy * line_tracking_height / raw_frame.rows;
// 边界过滤: row 0~9 无可靠巡线数据
if (rl < 10 || rl >= line_tracking_height) continue;
if (left_line[rl] == -1 || right_line[rl] == -1) continue; // 丢线行
// 赛道内判断
if (g_cfg.cone_margin) {
// margin=1: 用框的左右边缘判断是否在赛道内
int bl = (g_boxes[i].cx - g_boxes[i].w/2) * line_tracking_width / raw_frame.cols;
int br = (g_boxes[i].cx + g_boxes[i].w/2) * line_tracking_width / raw_frame.cols;
if (br < left_line[rl] || bl > right_line[rl]) continue; // 框完全在赛道外
} else {
// margin=0: 用中心点判断
if (cl < left_line[rl] || cl > right_line[rl]) continue; // 中心在赛道外
}
// 取 cy 最大的 (最近的锥桶)
if (rl > cone_row) { cone_row = rl; cone_col = cl; }
cone_seen = true;
}
// ── 2. 去抖确认 ──
{
// 位置容差: 动态计算, 典型 row_tol=5, col_tol=10
int row_tol = std::max(2, line_tracking_height / 12);
int col_tol = std::max(3, line_tracking_width / 8);
if (cone_seen) {
if (g_cone_frames == 0) {
// 首次出现 → 记录位置, 开始计数
g_cone_last_row = cone_row; g_cone_last_col = cone_col;
g_cone_frames = 1;
} else if (std::abs(cone_row - g_cone_last_row) <= row_tol &&
std::abs(cone_col - g_cone_last_col) <= col_tol) {
// 位置接近 → 继续计数
g_cone_frames++;
g_cone_last_row = cone_row; g_cone_last_col = cone_col;
} else {
// 位置突变 → 视为新锥桶, 重新开始计数
g_cone_frames = 1;
g_cone_last_row = cone_row; g_cone_last_col = cone_col;
}
} else {
// 未检测到 → 衰减
if (g_cone_frames > 0) g_cone_frames = std::max(0, g_cone_frames - 1);
}
}
g_cone_confirmed = (g_cone_frames >= g_cfg.cone_min_frames);
static int cdbg = 0;
if (g_cfg.debug && ++cdbg >= 15) { cdbg = 0;
printf("[CONE] frames=%d confirm=%d hold=%d return=%d\n",
g_cone_frames, g_cone_confirmed, g_cone_hold_ctr, g_cone_return_ctr);
}
// ── 3. 两阶段生命周期: 确认 → 保持衰减 ──
if (g_cone_confirmed) {
g_cone_hold_ctr = 1;
g_cone_return_ctr = 0;
// 只在锥桶实际可见时更新冻结位置和回弹方向,
// 防止 cone_row=-1 覆盖有效值 + mid_line[-1] 越界
if (cone_seen) {
g_cone_hold_src_row = cone_row;
g_cone_hold_src_col = cone_col;
// double ms = mid_line_raw[cone_row];
// g_cone_return_dir = (cone_col < ms) ? -1.0 : 1.0;
}
} else if (g_cone_hold_ctr > 0 && g_cone_hold_ctr <= g_cfg.cone_hold_frames) {
// 保持衰减阶段: 锥桶消失, 变形量逐帧衰减
g_cone_hold_ctr++;
// if (g_cone_hold_ctr > g_cfg.cone_hold_frames)
// g_cone_return_ctr = 1; // 保持结束 → 启动回弹
} /* else if (g_cone_return_ctr > 0 && g_cone_return_ctr <= g_cfg.cone_return_frames) {
// 回弹阶段: 朝锥桶方向反推, 帮助车回到赛道中央
g_cone_return_ctr++;
} */
// 回弹结束 → 清零所有状态, 避免计数器卡死
// if (g_cone_return_ctr > g_cfg.cone_return_frames) {
// printf("[CONE] ★ RETURN END\n");
// g_cone_hold_src_row = -1;
// g_cone_hold_ctr = 0;
// g_cone_return_ctr = 0;
// }
// 生命周期诊断 (debug 模式下每 5 帧打印)
// static int cone_ldbg = 0;
// if ((g_cone_confirmed || g_cone_hold_ctr > 0 || g_cone_return_ctr > 0) && ++cone_ldbg >= 5) {
// cone_ldbg = 0;
// printf("[CONE-DBG] seen=%d frm=%d conf=%d hold=%d/%d ret=%d/%d src_row=%d ret_dir=%.0f\n",
// cone_seen, g_cone_frames, g_cone_confirmed,
// g_cone_hold_ctr, g_cfg.cone_hold_frames,
// g_cone_return_ctr, g_cfg.cone_return_frames,
// g_cone_hold_src_row, g_cone_return_dir);
// }
// 三个阶段都不活跃 → 不做变形
if (!cone_is_slow()) return;
// ── 4. 中线变形 ──
// confirmed+可见 → 实时位置; 否则(confirmed+不可见 / 保持 / 回弹) → 冻结位置
int src_row = (g_cone_confirmed && cone_seen) ? cone_row : g_cone_hold_src_row;
int src_col = (g_cone_confirmed && cone_seen) ? cone_col : g_cone_hold_src_col;
if (src_row <= 10) return;
double rng = (double)g_cfg.cone_avoid_range; // 斜坡陡峭度
double half_w = line_tracking_width / 2.0;
double ms = mid_line_raw[src_row];
// 推离方向: 锥桶在原始中线左侧 → 推右(+1), 右侧 → 推左(-1)
double dir = (src_col < ms) ? 1.0 : -1.0;
// 保持阶段衰减: 从 1.0 线性降到 0.0
double decay = g_cone_confirmed ? 1.0 : (1.0 - (double)g_cone_hold_ctr / g_cfg.cone_hold_frames);
// ── 4a. 避开阶段: 推离锥桶 (row 10 ~ 底部, 确保前瞻行一定被覆盖) ──
// if (g_cone_return_ctr == 0) {
for (int row = 10; row < line_tracking_height; ++row) {
// 斜坡: row=10 处 t=0(不推), 随行号增大 t→1(满推)
double t = std::clamp(((row - 10) / rng), 0.0, 1.0);
double push = t * g_cfg.cone_avoid_gain * half_w * dir * decay;
double nm = std::clamp(mid_line[row] + push,
(double)left_line[row] + 2.0,
(double)right_line[row] - 2.0);
mid_line[row] = (int)(nm + 0.5);
}
// }
// ── 4b. 回弹: 锥桶消失后朝锥桶方向反推 ──
// if (g_cone_return_ctr > 0 && g_cone_return_ctr <= g_cfg.cone_return_frames) {
// static int rdbg = 0;
// if (++rdbg >= 10) { rdbg = 0;
// printf("[CONE] return frame=%d dir=%.0f gain=%.1f\n",
// g_cone_return_ctr, g_cone_return_dir, g_cfg.cone_return_gain);
// }
// int lt_h = line_tracking_height;
// double ret_rng = rng * 1.2; // 回弹斜坡比避让略缓
// double ret_hw = half_w;
// double ret_dir = g_cone_return_dir; // 与避让方向相反
// // 回弹衰减: 从 1.0 线性降到 0.0
// double ret_dec = 1.0 - (double)g_cone_return_ctr / g_cfg.cone_return_frames;
// // 回弹覆盖全部有效行 (row 10 ~ lt_h-1), 不限于锥桶位置
// for (int row = 10; row < lt_h; ++row) {
// double t = std::clamp((row - 10) / ret_rng, 0.0, 1.0);
// double push = t * g_cfg.cone_return_gain * ret_hw * ret_dir * ret_dec;
// double nm = std::clamp(mid_line[row] + push,
// (double)left_line[row] + 2.0,
// (double)right_line[row] - 2.0);
// mid_line[row] = (int)(nm + 0.5);
// }
// }
}
// ═══════════════════════════════════════════════════════════
// 急弯检测 — 扫描左右边界纵向梯度
//
// 5 行窗口内边界水平位移 > 15 像素 → 直角弯
// ═══════════════════════════════════════════════════════════
static bool g_sharp_turn = false;
static void detect_sharp_turn()
{
g_sharp_turn = false;
const int W = 3; // 窗口大小(行)
const int THR = 20; // 位移阈值(像素)
int lt_h = line_tracking_height;
for (int r = lt_h - 1; r >= 10 + W; --r) {
if (left_line[r] == -1 || left_line[r-W] == -1) continue;
if (std::abs(left_line[r] - left_line[r-W]) > THR) { g_sharp_turn = true; return; }
}
for (int r = lt_h - 1; r >= 10 + W; --r) {
if (right_line[r] == -1 || right_line[r-W] == -1) continue;
if (std::abs(right_line[r] - right_line[r-W]) > THR) { g_sharp_turn = true; return; }
}
}
// ═══════════════════════════════════════════════════════════
// Step 7: 舵机控制
//
// 读取 mid_line[foresee/calc_scale] 处的中线位置,
// 转换为显示空间偏差 → 归一化 → 乘增益 → PWM 占空比。
//
// 数学:
// deviation = mid_line[check_row] × calc_scale - newWidth/2 (像素)
// deviation -= center_bias (偏置修正)
// g_steer_deviation = deviation / (newWidth/2) (归一化 [-1,1])
//
// |deviation| < deadband → 直行 (1,500,000 ns)
// 否则: offset = norm × steer_gain × 300,000
// duty = clamp(1,500,000 + offset, 1,200,000, 1,800,000)
//
// 舵机量程: 1,200,000 ~ 1,800,000 ns, 中位 1,500,000 ns
// ═══════════════════════════════════════════════════════════
static void steering_update()
{
if (!g_cfg.start) return;
// 斑马线起步后延迟2s, 再3s内缩短前瞻, 先走稳再改
double eff_foresee = g_cfg.foresee;
if (g_zstate == Z_COOLDOWN && time(nullptr) - g_ztime >= 2
&& time(nullptr) - g_ztime < 5)
eff_foresee = 24;
int check_row = (int)eff_foresee / calc_scale;
if (check_row < 0 || check_row >= line_tracking_height) return;
if (mid_line[check_row] == -1) return; // 丢线行 → 跳过
// 巡线空间中线 → 显示空间偏差
double deviation = mid_line[check_row] * calc_scale - newWidth / 2;
deviation -= g_cfg.center_bias; // 机械偏置修正
// 归一化到 [-1, 1], 供弯道减速使用
g_steer_deviation = deviation / (newWidth / 2.0);
// 斑马线冷却后 3.3~5 秒:一次机会,偏差出现->方向锁定打满->方向翻转/回正即退出
if (g_zstate == Z_COOLDOWN) {
time_t elapsed = time(nullptr) - g_ztime;
if (elapsed >= 3.3 && elapsed < 5 && !g_bangbang_used) {
if (!g_bangbang_active) {
// 序列未开始,等偏差出现才启动
if (std::abs(deviation) < g_cfg.deadband) {
g_steer_deviation = 0.0;
servo.setDutyCycle(1500000);
return;
}
g_bangbang_active = true;
g_bangbang_dir = (deviation > 0) ? 1 : -1;
} else {
// 序列已启动:检查方向是否翻转或回到死区
bool same_dir = (deviation > 0 && g_bangbang_dir > 0) ||
(deviation < 0 && g_bangbang_dir < 0);
if (!same_dir || std::abs(deviation) < g_cfg.deadband) {
// 方向翻转或回正 -> 机会用完,退出,后续走正常线性转向
g_bangbang_active = false;
g_bangbang_used = true;
} else {
// 同方向,继续打满
unsigned int duty = (g_bangbang_dir > 0) ? 1800000 : 1200000;
servo.setDutyCycle(duty);
g_steer_deviation = (g_bangbang_dir > 0) ? 1.0 : -1.0;
return;
}
}
}
}
// 平滑线性转向: 偏差从死区边界开始缩放,消除死区断崖
double effective = std::abs(deviation) - g_cfg.deadband;
if (effective <= 0) {
servo.setDutyCycle(1500000);
} else {
double half_w = newWidth / 2.0;
double norm = effective / (half_w - g_cfg.deadband);
double sign = (deviation > 0) ? 1.0 : -1.0;
double offset = norm * g_cfg.steer_gain * 300000.0 * sign;
double duty_ns = std::clamp(1500000.0 + offset, 1200000.0, 1800000.0);
servo.setDutyCycle((unsigned int)duty_ns);
}
}
// ═══════════════════════════════════════════════════════════
// Step 8: 电机控制
//
// 弯道减速:
// factor = 1.0 - |g_steer_deviation| × curve_slope
// factor = max(factor, curve_min)
// final_spd = target_speed × factor
//
// 锥桶/挡板减速 (代码存在但已注释):
// cone_is_slow() → factor = min(factor, cone_speed)
// lidar_is_active() → factor = min(factor, lidar_speed)
//
// 调用 ControlUpdate(final_spd, block) 执行:
// block = zebra_block || tl_block → 编码器比例刹车
// 否则 → 开环占空比驱动
//
// 起步弹射: 启动/斑马线恢复后 1.5x 速度持续 0.5s 快速起步
// ═══════════════════════════════════════════════════════════
static struct timespec g_boost_end = {0, 0};
static double g_boost_mul = 1.0;
static constexpr double BOOST_SECS = 0.7;
static constexpr double BOOST_MUL = 1.6;
static constexpr double BOOST_GREEN_SECS = 0.2;
static constexpr double BOOST_GREEN_MUL = 2.0;
static void trigger_boost(double secs, double mul)
{
g_boost_mul = mul;
clock_gettime(CLOCK_MONOTONIC, &g_boost_end);
g_boost_end.tv_nsec += (long)(secs * 1e9);
if (g_boost_end.tv_nsec >= 1000000000L) {
g_boost_end.tv_sec += 1;
g_boost_end.tv_nsec -= 1000000000L;
}
}
static double boost_factor()
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > g_boost_end.tv_sec) return 1.0;
if (now.tv_sec == g_boost_end.tv_sec && now.tv_nsec >= g_boost_end.tv_nsec) return 1.0;
return g_boost_mul;
}
static void motor_update(bool zebra_block, bool tl_block)
{
// 起步弹射检测
static int prev_start = 0;
static ZState prev_zstate = Z_NORMAL;
static TLState prev_tlstate = TL_NORMAL;
if (g_cfg.start && !prev_start) trigger_boost(BOOST_SECS, BOOST_MUL); // 启动
if (prev_zstate == Z_STOP && g_zstate == Z_COOLDOWN) trigger_boost(BOOST_SECS, BOOST_MUL); // 斑马线起步
if (prev_tlstate == TL_WAIT_GREEN && g_tl_state == TL_NORMAL) trigger_boost(BOOST_GREEN_SECS, BOOST_GREEN_MUL); // 绿灯起步
prev_start = g_cfg.start;
prev_zstate = g_zstate;
prev_tlstate = g_tl_state;
double spd;
if (boost_factor() > 1.0) {
spd = 26.0; // 起步弹射
} else if (g_zstate == Z_COOLDOWN) {
spd = 16.0; // 冷却期基础速度
time_t elapsed = time(nullptr) - g_ztime;
if (elapsed >= 3.3 && elapsed < 5)
spd = std::min(spd, 13.0); // 3.3~5s 降速
} else {
spd = target_speed; // 正常行驶
}
// 弯道减速: 舵机偏差越大 → 速度越低
double curve = 1.0 - std::abs(g_steer_deviation) * g_cfg.curve_slope;
if (curve < g_cfg.curve_min) curve = g_cfg.curve_min;
// 取最低倍率: 弯道 / 锥桶 / 挡板 三者不叠加, 取最小值
double factor = curve;
// if (g_lost_rows > 15)
// factor = std::min(factor, 0.6);
// if (cone_is_slow() && g_zstate != Z_STOP && g_tl_state == TL_NORMAL)
// factor = std::min(factor, g_cfg.cone_speed);
// if (lidar_is_active() && g_zstate != Z_STOP && g_tl_state == TL_NORMAL)
// factor = std::min(factor, g_cfg.lidar_speed);
double final_spd = spd * factor;
// 每 30 帧打印一次速度调试信息
static int dbg = 0;
if (++dbg >= 30) { dbg = 0;
printf("[SPD] base=%.0f dev=%.2f curve=%.2f final=%.1f\n",
spd, g_steer_deviation, curve, final_spd);
}
// 传递给 control.cpp: 刹车信号 = 斑马线停车 OR 红绿灯停车
ControlUpdate(final_spd, zebra_block || tl_block);
}
// ═══════════════════════════════════════════════════════════
// Step 9: LCD 渲染
//
// 流程:
// 1. 每 10 帧从 g_cfg.showImg 刷新 g_lcd_on 开关
// 2. track (128/0 灰度) → resize → GRAY2BGR → 居中拷贝到 lcd_fbImage
// 3. 绘制边界线: 红=左边界, 绿=右边界, 蓝=中线
// 4. 绘制检测框: 橙=锥桶, 紫=斑马线 (跳过红绿灯框)
// 5. 绘制状态指示: L=激光避障, R=红灯, G=等绿灯, S=斑马线停, C=冷却, N=正常
// 6. BGR → RGB565 → 写入 /dev/fb0 mmap
// ═══════════════════════════════════════════════════════════
static cv::Mat lcd_fbImage, lcd_resized, lcd_colored;
static void lcd_clear_screen()
{
if (fb_buffer && g_fb_size > 0)
std::memset(fb_buffer, 0, g_fb_size);
}
static void lcd_show_stop_image()
{
if (g_stop_image.empty() || !fb_buffer) return;
// 若图片尺寸与屏幕不一致则缩放
cv::Mat display_img = g_stop_image;
if (display_img.cols != screenWidth || display_img.rows != screenHeight)
cv::resize(display_img, display_img, cv::Size(screenWidth, screenHeight));
convertMatToRGB565(display_img, fb_buffer, screenWidth, screenHeight);
}
static void lcd_render()
{
// 斑马线停车期间由 CameraHandler 单独控制清屏+图片, 此处不渲染
if (g_zstate == Z_STOP) return;
// 每 10 帧刷新 LCD 开关, 避免每帧读文件
static int lcd_check = 0;
if (--lcd_check < 0) { g_lcd_on = g_cfg.showImg; lcd_check = 10; }
if (!g_lcd_on) return;
// 创建黑底画布
lcd_fbImage.create(screenHeight, screenWidth, CV_8UC3);
lcd_fbImage.setTo(cv::Scalar(0, 0, 0));
// 赛道灰度图居中显示
cv::resize(track, lcd_resized, cv::Size(newWidth, newHeight));
cv::cvtColor(lcd_resized, lcd_colored, cv::COLOR_GRAY2BGR);
cv::Rect roi((screenWidth - newWidth) / 2, (screenHeight - newHeight) / 2, newWidth, newHeight);
lcd_colored.copyTo(lcd_fbImage(roi));
// ── 边界线: 逐行绘制 ──
for (int y = 0; y < line_tracking_height; ++y) {
int lx = (int)(left_line[y] * calc_scale), ly = y * calc_scale;
int rx = (int)(right_line[y] * calc_scale);
int mx = (int)(mid_line[y] * calc_scale);
cv::line(lcd_fbImage(roi), cv::Point(lx, ly), cv::Point(lx, ly), cv::Scalar(0, 0, 255), calc_scale); // 红=左
cv::line(lcd_fbImage(roi), cv::Point(rx, ly), cv::Point(rx, ly), cv::Scalar(0, 255, 0), calc_scale); // 绿=右
cv::line(lcd_fbImage(roi), cv::Point(mx, ly), cv::Point(mx, ly), cv::Scalar(255, 0, 0), calc_scale); // 蓝=中
}
// ── 检测框 (跳过红绿灯, 不画框) ──
float bx = (float)newWidth / raw_frame.cols, by = (float)newHeight / raw_frame.rows;
for (int i = 0; i < g_box_count; ++i) {
if (g_boxes[i].cls == MD_RED || g_boxes[i].cls == MD_GREEN) continue;
// 模型空间 → 显示空间
int x1 = (int)((g_boxes[i].cx - g_boxes[i].w/2) * bx);
int y1 = (int)((g_boxes[i].cy - g_boxes[i].h/2) * by);
int x2 = (int)((g_boxes[i].cx + g_boxes[i].w/2) * bx);
int y2 = (int)((g_boxes[i].cy + g_boxes[i].h/2) * by);
x1 = std::max(0, std::min(newWidth - 1, x1));
y1 = std::max(0, std::min(newHeight - 1, y1));
x2 = std::max(0, std::min(newWidth - 1, x2));
y2 = std::max(0, std::min(newHeight - 1, y2));
// 颜色: 锥桶=橙, 斑马线=紫, 其他=绿
cv::Scalar color(0, 255, 0);
if (g_boxes[i].cls == MD_ZEBRA) color = cv::Scalar(255, 0, 255); // 紫
else if (g_boxes[i].cls == MD_CONE) color = cv::Scalar(0, 165, 255); // 橙
cv::rectangle(lcd_fbImage(roi), cv::Point(x1, y1), cv::Point(x2, y2), color, 2);
// 标注: 类别编号 + 置信度百分比
char lab[16]; snprintf(lab, 16, "%d %.0f", g_boxes[i].cls, g_boxes[i].conf * 100);
cv::putText(lcd_fbImage(roi), lab, cv::Point(x1 + 2, y1 + 10),
cv::FONT_HERSHEY_SIMPLEX, 0.3, color, 1);
}
// ── 状态指示 (左下角) ──
// L=激光避障, R=红灯停, G=等绿灯, S=斑马线停, C=冷却, N=正常
char ztxt[8]; int zi = 0;
// if (lidar_is_active()) ztxt[zi++] = 'L';
if (g_tl_state == TL_STOP) ztxt[zi++] = 'R';
else if (g_tl_state == TL_WAIT_GREEN) ztxt[zi++] = 'G';
if (g_zstate == Z_STOP) ztxt[zi++] = 'S';
else if (g_zstate == Z_COOLDOWN) ztxt[zi++] = 'C';
else ztxt[zi++] = 'N';
ztxt[zi] = 0;
cv::putText(lcd_fbImage(roi), ztxt, cv::Point(2, newHeight - 4),
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 255, 255), 1);
// BGR → RGB565 → /dev/fb0
convertMatToRGB565(lcd_fbImage, fb_buffer, screenWidth, screenHeight);
}
// ═══════════════════════════════════════════════════════════
// Step 10: FPS 统计
//
// 每 15 帧输出一行:
// fps: 总帧率
// cap: 取帧耗时(ms)
// vis: 视觉巡线耗时(ms)
// mdl: 模型推理耗时(ms) (管道末尾, 不阻塞舵机)
// ctl: 控制耗时(ms) (状态机+舵机+电机)
// lat: 端到端延时(ms) (取帧开始→舵机输出)
//
// ts[] 时间戳由 CameraHandler 在各步骤间采集
// ═══════════════════════════════════════════════════════════
static void fps_log(struct timespec *ts)
{
static int fc = 0;
static struct timespec tf0;
if (fc == 0) clock_gettime(CLOCK_MONOTONIC, &tf0);
fc++;
if (fc % 15 != 0) return; // 每 15 帧输出一次
struct timespec tf1;
clock_gettime(CLOCK_MONOTONIC, &tf1);
double dt = (tf1.tv_sec - tf0.tv_sec) + (tf1.tv_nsec - tf0.tv_nsec) * 1e-9;
double ms_cap = (ts[1].tv_sec - ts[0].tv_sec) * 1000.0 + (ts[1].tv_nsec - ts[0].tv_nsec) * 1e-6;
double ms_vis = (ts[2].tv_sec - ts[1].tv_sec) * 1000.0 + (ts[2].tv_nsec - ts[1].tv_nsec) * 1e-6;
double ms_mdl = (ts[5].tv_sec - ts[4].tv_sec) * 1000.0 + (ts[5].tv_nsec - ts[4].tv_nsec) * 1e-6;
double ms_ctl = (ts[4].tv_sec - ts[2].tv_sec) * 1000.0 + (ts[4].tv_nsec - ts[2].tv_nsec) * 1e-6;
double ms_lat = (ts[3].tv_sec - ts[0].tv_sec) * 1000.0 + (ts[3].tv_nsec - ts[0].tv_nsec) * 1e-6;
printf("fps=%.1f | cap=%.0f vis=%.0f mdl=%.0f ctl=%.0f lat=%.0f ms\r",
fc / dt, ms_cap, ms_vis, ms_mdl, ms_ctl, ms_lat);
fflush(stdout);
}
// ═══════════════════════════════════════════════════════════
// CameraHandler — 每帧主流程 (10 步流水线)
//
// 由 main.cpp 主循环调用, 单线程顺序执行。
//
// 附加逻辑:
// - debug 模式每 30 帧重载全部配置 (cfg_load_all)
//
// ts[6] 时间戳分布 (按执行顺序):
// ts[0]: capture_frame 取帧开始
// ts[1]: capture_frame 取帧结束
// ts[2]: image_main 视觉巡线结束
// ts[3]: steering_update 舵机输出结束 (端到端延时终点)
// ts[4]: motor_update 电机控制结束
// ts[5]: run_model_inference 模型推理结束 (管道末尾)
// ═══════════════════════════════════════════════════════════
int CameraHandler(void)
{
// debug 模式: 每 30 帧热重载配置文件
static int reloadCnt = 0;
if (g_cfg.debug && ++reloadCnt >= 30) { reloadCnt = 0; cfg_load_all(); }
// 嵌入式优化: 每 150 帧回收 glibc malloc 缓存的空闲页
static int trimCnt = 0;
// if (++trimCnt >= 150) { trimCnt = 0; malloc_trim(0); }
struct timespec ts[6];
// 1. 取帧 + MJPEG 解码
if (capture_frame(&ts[0], &ts[1]) < 0) return -1;
// 2. 保存图像 (debug)
save_image_if_requested();
// 3. 视觉巡线 → left/right/mid_line[]
image_main();
mid_line_raw = mid_line;
clock_gettime(CLOCK_MONOTONIC, &ts[2]);
// 4. 激光避障 → 修改 mid_line[] (在模型推理之前, 优先级高于锥桶)
// lidar_avoid_process();
// 5. 场景识别状态机 (使用上一帧的模型检测结果 g_boxes[])
bool zebra_block = zebra_process(); // 5a. 斑马线
bool tl_block = traffic_light_process(); // 5b. 红绿灯
cone_detect_and_deform(); // 5c. 锥桶 → 修改 mid_line[]
// detect_sharp_turn(); // 急弯检测
// 6. 舵机控制 (读取变形后的 mid_line)
steering_update();
clock_gettime(CLOCK_MONOTONIC, &ts[3]); // 舵机输出结束 = 端到端延时终点
// ── 临时测试: 锥桶确认后 1s 强制右转 0.5s ──
// {
// static int cone_test_timer = -1;
// static bool cone_test_need_clr = false;
//
// if (cone_test_need_clr && !g_cone_confirmed)
// cone_test_need_clr = false;
//
// if (g_cone_confirmed && cone_test_timer < 0 && !cone_test_need_clr) {
// cone_test_timer = 0;
// printf("[CONE-TEST] 锥桶确认, 开始计时\n");
// }
// if (cone_test_timer >= 0) {
// cone_test_timer++;
// if (cone_test_timer == 30)
// printf("[CONE-TEST] 强制右转开始\n");
// if (cone_test_timer >= 30 && cone_test_timer < 38)
// servo.setDutyCycle(1700000);
// if (cone_test_timer >= 38) {
// printf("[CONE-TEST] 强制右转结束\n");
// cone_test_timer = -1;
// cone_test_need_clr = true;
// }
// }
// }
// 7. 电机控制 (弯道减速 + 刹车)
motor_update(zebra_block, tl_block);
clock_gettime(CLOCK_MONOTONIC, &ts[4]);
// 8. 模型推理 → g_boxes[] (放到最后, 供下一帧状态机使用, 不阻塞舵机)
run_model_inference();
clock_gettime(CLOCK_MONOTONIC, &ts[5]);
// 9. LCD 渲染
// 斑马线停车状态切换时清屏/显示提示图片,仅切换时操作,不每帧准备缓存
static ZState prev_zstate = Z_NORMAL;
if (g_zstate == Z_STOP && prev_zstate != Z_STOP) {
lcd_clear_screen();
lcd_show_stop_image();
} else if (g_zstate != Z_STOP && prev_zstate == Z_STOP) {
lcd_clear_screen();
}
// 进入 Z_COOLDOWN 时重置打满状态机
if (g_zstate == Z_COOLDOWN && prev_zstate != Z_COOLDOWN) {
g_bangbang_used = false;
g_bangbang_active = false;
}
prev_zstate = g_zstate;
lcd_render();
// 10. FPS 日志
fps_log(ts);
return 0;
}