Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f96df39d4 | |||
| 0502948bcd | |||
| cbb54b4882 | |||
| 66fb213861 | |||
| 97e06314cc | |||
| ad48c955d9 | |||
| b69a5ff5bc | |||
| a43ff769d4 | |||
| afc839e8cb | |||
| 08b00d6c97 | |||
| b9141eaf8a | |||
| e720009794 | |||
| 8ee66fe444 | |||
| 7b220f5c3b | |||
| 7284a8d037 | |||
| 54f13984a9 | |||
| 670f84131b |
+9
-5
@@ -1,14 +1,18 @@
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
build/*
|
||||
build2/*
|
||||
build3/*
|
||||
build4/*
|
||||
build_lsx/*
|
||||
build_new/*
|
||||
test/
|
||||
|
||||
ZebraCrossing_Detection-master/
|
||||
ZebraCrossing_Detection-master.zip
|
||||
smartcar2_code.txt
|
||||
smartcar_test
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"python-envs.defaultEnvManager": "ms-python.python:conda",
|
||||
"python-envs.defaultPackageManager": "ms-python.python:conda"
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wall")
|
||||
project(smartcar_demo2 VERSION 0.1.0 LANGUAGES C CXX)
|
||||
|
||||
# OpenCV 设备端路径
|
||||
set(OpenCV_DIR /home/spdis/loongson/opencv-4.13.0/loongson/lib/cmake/opencv4)
|
||||
set(OpenCV_DIR /mnt/D/PPPProgram/smartcar/opencv-4.13.0/loongson/lib/cmake/opencv4)
|
||||
find_package(OpenCV REQUIRED)
|
||||
include_directories(${OpenCV_INCLUDE_DIRS})
|
||||
message(STATUS "OpenCV Include Directories: ${OpenCV_INCLUDE_DIRS}")
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ SET(CROSS_COMPILE 1)
|
||||
IF(CROSS_COMPILE)
|
||||
SET(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR loongson)
|
||||
SET(TOOLCHAIN_DIR "/opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6")
|
||||
SET(TOOLCHAIN_DIR "/home/spdis/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6")
|
||||
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/loongarch64-linux-gnu-g++)
|
||||
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/loongarch64-linux-gnu-gcc)
|
||||
|
||||
|
||||
+21
-8
@@ -247,9 +247,20 @@ ZNORMAL 期间:
|
||||
└──────────┘
|
||||
|
||||
NORMAL: 允许通行,检测斑马线
|
||||
STOP: 刹停 4 秒,g_zstate==Z_STOP 传给 motor_update → 编码器刹车
|
||||
STOP: 刹停 3 秒,g_zstate==Z_STOP 传给 motor_update → 编码器刹车
|
||||
LCD 显示 pedestrian_stop.png(不受 showImg 控制),进入/退出时清屏
|
||||
COOLDOWN: 恢复行驶但斑马线状态机停止检测 5 秒(防止重复触发)
|
||||
仅跳过检测,不影响其他功能(舵机/巡线正常)
|
||||
|
||||
### 斑马线冷却期控制策略 (Z_COOLDOWN)
|
||||
|
||||
| 时间窗口 | 控制 | 说明 |
|
||||
|----------|------|------|
|
||||
| 0~2s | 全速起步 | boost 1.5x 速度持续 0.7s |
|
||||
| 2~5s | foresee=24 | 缩短前瞻走稳 |
|
||||
| 3.3~5s | 降速上限 13 + bang-bang 打满 | 一次机会:偏差出现→方向锁定打满→方向翻转/回正即退出 |
|
||||
|
||||
**bang-bang 逻辑**(`steering_update()`):Z_COOLDOWN 3.3~5s 内,偏差超出死区后锁定方向一次性打满(左满 1.2ms / 右满 1.8ms),直到偏差方向翻转或回到死区退出,后续走正常线性转向。
|
||||
```
|
||||
|
||||
**关键变量:**
|
||||
@@ -356,12 +367,14 @@ lcd_render():
|
||||
| `./start` | int(0/1) | 0 | 0 | 使能开关,1=电机运行 |
|
||||
| `./debug` | int(0/1) | 0 | (不写入) | 每30帧重载配置+允许存图 |
|
||||
| `./showImg` | int(0/1) | 0 | (不写入) | LCD 显示(每10帧轮询→g_lcd_on) |
|
||||
| `./foresee` | double | 80 | 40 | 舵机前瞻行 (显示空间像素) |
|
||||
| `./foresee` | double | 80 | 40 | 舵机前瞻行 (显示空间像素, Z_COOLDOWN 2~5s 内缩短为 24) |
|
||||
| `./foresee_lost_scale` | double | 0.7 | 0.7 | 丢线时前瞻缩放 (<1=看更远) |
|
||||
| `./sharp_turn_scale` | double | 0.5 | 0.5 | 急弯时前瞻缩放 (<1=看更远) |
|
||||
| `./deadband` | double | 5 | 8 | 舵机死区 (显示空间像素) |
|
||||
| `./steer_gain` | double | 1.0 | 1.5 | 舵机增益 |
|
||||
| `./center_bias` | double | 0 | (不写入) | 中线偏置修正 |
|
||||
| `./zebrasee` | double | 60 | (不写入) | 斑马线近界阈值 (模型空间cy) |
|
||||
| `./destfps` | double | 30* | (不写入) | 目标帧率 (*代码兜底值) |
|
||||
| `./destfps` | double | - | (不写入) | 目标帧率 |
|
||||
| `./saveImg` | int(0/1) | - | (不写入) | 保存帧图像 (需 debug=1) |
|
||||
| `./cone_avoid_gain` | double | 0.3 | 0.3 | 锥桶中线变形推离量 (归一化) |
|
||||
| `./cone_avoid_range` | int | 30 | 30 | 变形斜坡陡峭度 |
|
||||
@@ -402,7 +415,6 @@ lcd_render():
|
||||
|
||||
| 文件 | 功能 | 排除原因 |
|
||||
|---|---|---|
|
||||
| `zebra_detect.cpp` | 经典斑马线检测 | 已被 Mild 模型替代 |
|
||||
| `PIDController.cpp` | 位置式/增量式 PID | 电机开环直驱,无调用点 |
|
||||
| `serial.cpp` | VOFA 串口可视化 | 未使用 |
|
||||
|
||||
@@ -412,15 +424,16 @@ lcd_render():
|
||||
|
||||
---
|
||||
|
||||
## 模型:Mild v12
|
||||
## 模型:Mild v13
|
||||
|
||||
- **推理引擎**:`src/model_v10.{hpp,cpp}`(名称为兼容保留,内部为 Mild v3)
|
||||
- **推理引擎**:`src/model_v10.{hpp,cpp}`(名称为兼容保留,内部为 Mild Mega r2 v3)
|
||||
- **输入**:160×120 BGR
|
||||
- **输出**:4 类 + 背景 → 9 通道:0=锥桶, 1=红灯, 2=绿灯, 3=斑马线
|
||||
- **架构**:3→9→9→13→13→19→19→19→44→44→64→64 → head(64→64,dw) → out(64→9)
|
||||
- **阈值**:`{0.65, 0.75, 0.80, 0.82}`(锥桶/红灯/绿灯/斑马线)
|
||||
- **权重文件**:`mild_v12.bin`(105KB 自定义二进制格式)
|
||||
- **阈值**:`{0.80, 0.70, 0.80, 0.85}`(锥桶/红灯/绿灯/斑马线)
|
||||
- **权重文件**:`mild_v12.bin`(103KB 自定义二进制格式,实际模型为 Mild v13,沿用旧名)
|
||||
- **推理频率**:每 2 帧一次(跳帧节省 CPU)
|
||||
- **转换工具**:`tools/export_mild_v13.py`(PyTorch .pth → .bin)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# 比赛参数固化
|
||||
|
||||
> 截至 2026-07-04,16速,45秒两圈
|
||||
|
||||
## 设备实际配置文件 (`/home/root/smartcar2/`)
|
||||
|
||||
```bash
|
||||
speed=16 # 目标速度 (% 占空比)
|
||||
deadband=8 # 舵机死区 (显示空间像素)
|
||||
steer_gain=1.5 # 舵机增益
|
||||
foresee=40 # 舵机前瞻行 (显示空间像素)
|
||||
foresee_lost_scale=0.6# 丢线时前瞻缩放
|
||||
sharp_turn_scale=0.5 # 急弯前瞻缩放
|
||||
zebrasee=18 # 斑马线近界阈值 (模型空间 cy)
|
||||
curve_slope=0.2 # 弯道减速斜率
|
||||
curve_min=0.8 # 弯道最低速度倍率
|
||||
cone_avoid_gain=0.3 # 锥桶推离力度
|
||||
cone_avoid_range=30 # 锥桶斜坡陡峭度
|
||||
cone_speed=0.5 # 锥桶速度倍率 (当前未生效)
|
||||
cone_min_frames=3 # 锥桶确认帧数
|
||||
cone_margin=0 # 0=中心点, 1=框边缘
|
||||
cone_thresh=0.80 # 锥桶置信度阈值
|
||||
cone_hold_frames=30 # 锥桶消失后保持帧数
|
||||
brake_scale=10 # 刹车速度→占空比系数
|
||||
brake_max=10000 # 最大刹车占空比 (ns)
|
||||
lidar_enable=1 # 激光避障 (代码中已禁用)
|
||||
start=1 # 电机使能运行中
|
||||
debug=1 # 热调参模式 (每30帧重载配置)
|
||||
showImg=0 # LCD 预览关闭
|
||||
```
|
||||
|
||||
## 代码硬编码行为
|
||||
|
||||
### 斑马线状态机
|
||||
- **进入 Z_STOP**: 播报语音 + LCD 显示 `pedestrian_stop.png`
|
||||
- **停车**: 3 秒编码器刹车
|
||||
- **冷却 Z_COOLDOWN**: 5 秒内不检测斑马线
|
||||
|
||||
### 斑马线冷却期控制 (Z_COOLDOWN)
|
||||
| 时间 | 控制 |
|
||||
|------|------|
|
||||
| 0~0.7s | boost 1.5x 起步 |
|
||||
| 2~5s | foresee 缩短为 24 |
|
||||
| 3.3~5s | 降速上限 13 + 方向锁定一次性打满 |
|
||||
|
||||
### 斑马线冷却期 bang-bang 转向
|
||||
- 3.3~5 秒内部署
|
||||
- 偏差超出死区后锁定方向一次性打满
|
||||
- 方向翻转或回到死区即退出,机会用完
|
||||
- 退出后走正常线性转向
|
||||
|
||||
### 模型
|
||||
- **Mild v13** (文件名为 `mild_v12.bin`)
|
||||
- 输入 160×120 BGR,输出 4 类:锥桶/红灯/绿灯/斑马线
|
||||
- 阈值:`{0.80, 0.70, 0.80, 0.85}`
|
||||
- 推理频率:每 2 帧一次
|
||||
|
||||
### 硬件
|
||||
- **LCD**: 160×128 RGB565
|
||||
- **摄像头**: 640×480 MJPEG → 1/4 解码 160×120
|
||||
- **舵机**: pwmchip1/pwm0, 3ms 周期, 1.2~1.8ms 量程
|
||||
- **电机**: pwmchip8/pwm1+2, 20kHz, 开环占空比
|
||||
- **编码器**: GPIO67 脉冲, GPIO72 方向, 100ms 窗口测速
|
||||
- **语音**: I2C-2 地址 0x34
|
||||
|
||||
### 编译
|
||||
```bash
|
||||
cd smartcar2 && mkdir -p build && cd build
|
||||
cmake .. && make -j$(nproc)
|
||||
```
|
||||
- 工具链: `/opt/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.6`
|
||||
- OpenCV: 4.13.0 (交叉编译, 仅 core/imgproc/imgcodecs/videoio)
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* zebra_detect.h — 斑马线检测函数
|
||||
* ================================
|
||||
* 输入图片 → 返回是否有人行横道 + 距离
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
struct ZebraResult
|
||||
{
|
||||
bool detected; // true = 检测到斑马线
|
||||
int distance_px; // 斑马线下沿距图像下边框的像素距离, -1 = 未检测到
|
||||
};
|
||||
|
||||
ZebraResult detect_zebra_crossing(const cv::Mat &bgr_frame);
|
||||
@@ -7,6 +7,3 @@ target_link_libraries(lidar_test common_lib)
|
||||
|
||||
add_executable(remote_control remote_control.cpp)
|
||||
target_link_libraries(remote_control common_lib)
|
||||
|
||||
add_executable(lcd_test lcd_test.cpp)
|
||||
target_link_libraries(lcd_test common_lib ${OpenCV_LIBS})
|
||||
+15
-80
@@ -25,7 +25,6 @@
|
||||
#include "camera.h"
|
||||
#include "model_v10.hpp"
|
||||
#include "vl53l0x.h"
|
||||
#include "font_bitmap.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
@@ -218,76 +217,6 @@ static void play_zebra_audio()
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// LCD 中文文字渲染
|
||||
//
|
||||
// 使用 src/font_bitmap.h 中嵌入的 16x16 字模
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
static const uint8_t* find_glyph(uint32_t codepoint)
|
||||
{
|
||||
for (int i = 0; i < FONT_GLYPH_COUNT; ++i)
|
||||
if (FONT_GLYPHS[i].unicode == codepoint) return FONT_GLYPHS[i].bitmap;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint32_t utf8_decode(const char*& p)
|
||||
{
|
||||
unsigned char c = static_cast<unsigned char>(*p);
|
||||
if (c == 0) return 0;
|
||||
if ((c & 0x80) == 0) { ++p; return c; }
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
uint32_t v = c & 0x1F; ++p;
|
||||
v = (v << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
|
||||
return v;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0) {
|
||||
uint32_t v = c & 0x0F; ++p;
|
||||
v = (v << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
|
||||
v = (v << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
|
||||
return v;
|
||||
}
|
||||
if ((c & 0xF8) == 0xF0) {
|
||||
uint32_t v = c & 0x07; ++p;
|
||||
v = (v << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
|
||||
v = (v << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
|
||||
v = (v << 6) | (static_cast<unsigned char>(*p++) & 0x3F);
|
||||
return v;
|
||||
}
|
||||
++p;
|
||||
return '?';
|
||||
}
|
||||
|
||||
static void lcd_draw_glyph(cv::Mat& img, int x0, int y0, const uint8_t* glyph, const cv::Scalar& color)
|
||||
{
|
||||
for (int row = 0; row < FONT_SIZE; ++row) {
|
||||
for (int col = 0; col < FONT_SIZE; ++col) {
|
||||
if (glyph[row * FONT_SIZE + col] > 128) {
|
||||
int px = x0 + col;
|
||||
int py = y0 + row;
|
||||
if (px >= 0 && px < img.cols && py >= 0 && py < img.rows)
|
||||
img.at<cv::Vec3b>(py, px) = cv::Vec3b(static_cast<uchar>(color[0]),
|
||||
static_cast<uchar>(color[1]),
|
||||
static_cast<uchar>(color[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_draw_chinese_text(cv::Mat& img, const char* text, int x, int y, const cv::Scalar& color)
|
||||
{
|
||||
const char* p = text;
|
||||
int cx = x;
|
||||
while (*p) {
|
||||
uint32_t cp = utf8_decode(p);
|
||||
const uint8_t* glyph = find_glyph(cp);
|
||||
if (glyph) {
|
||||
lcd_draw_glyph(img, cx, y, glyph, color);
|
||||
cx += FONT_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CameraInit — 系统初始化
|
||||
//
|
||||
@@ -1033,12 +962,15 @@ static void steering_update()
|
||||
}
|
||||
}
|
||||
|
||||
if (std::abs(deviation) < g_cfg.deadband) {
|
||||
// 死区内 → 直行
|
||||
// 平滑线性转向: 偏差从死区边界开始缩放,消除死区断崖
|
||||
double effective = std::abs(deviation) - g_cfg.deadband;
|
||||
if (effective <= 0) {
|
||||
servo.setDutyCycle(1500000);
|
||||
} else {
|
||||
// 比例控制: 偏差 → 归一化 → × 增益 × 满偏占空比差
|
||||
double offset = deviation / (newWidth / 2.0) * g_cfg.steer_gain * 300000;
|
||||
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);
|
||||
}
|
||||
@@ -1102,13 +1034,16 @@ static void motor_update(bool zebra_block, bool tl_block)
|
||||
prev_zstate = g_zstate;
|
||||
prev_tlstate = g_tl_state;
|
||||
|
||||
double spd = target_speed * boost_factor();
|
||||
|
||||
// 斑马线起步后 3.3~5s 降速走稳
|
||||
if (g_zstate == Z_COOLDOWN) {
|
||||
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);
|
||||
spd = std::min(spd, 13.0); // 3.3~5s 降速
|
||||
} else {
|
||||
spd = target_speed; // 正常行驶
|
||||
}
|
||||
|
||||
// 弯道减速: 舵机偏差越大 → 速度越低
|
||||
|
||||
+10
-5
@@ -69,10 +69,12 @@ cv::Mat image_binerize(cv::Mat &frame)
|
||||
cv::cvtColor(frame, hsvImage, cv::COLOR_BGR2HSV);
|
||||
cv::split(hsvImage, hsvChannels);
|
||||
|
||||
cv::threshold(hsvChannels[0], binarizedFrame,
|
||||
0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
|
||||
cv::threshold(hsvChannels[1], output,
|
||||
0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
|
||||
cv::adaptiveThreshold(hsvChannels[0], binarizedFrame, 255,
|
||||
cv::ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv::THRESH_BINARY_INV, 11, 2);
|
||||
cv::adaptiveThreshold(hsvChannels[1], output, 255,
|
||||
cv::ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv::THRESH_BINARY_INV, 11, 2);
|
||||
|
||||
cv::bitwise_or(output, binarizedFrame, output);
|
||||
|
||||
@@ -104,7 +106,7 @@ cv::Mat image_binerize(cv::Mat &frame)
|
||||
// ============================================================
|
||||
cv::Mat find_road(cv::Mat &frame)
|
||||
{
|
||||
static cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3));
|
||||
static cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(7, 7));
|
||||
cv::morphologyEx(binarizedFrame, morphologyExFrame, cv::MORPH_OPEN, kernel);
|
||||
|
||||
static cv::Mat mask;
|
||||
@@ -173,6 +175,9 @@ void image_main()
|
||||
cv::merge(labCh, labFrame);
|
||||
cv::cvtColor(labFrame, resizedFrame, cv::COLOR_Lab2BGR);
|
||||
|
||||
// ── 1.6 高斯模糊 → 融合反光噪点 ────────────────────
|
||||
cv::GaussianBlur(resizedFrame, resizedFrame, cv::Size(5, 5), 0);
|
||||
|
||||
// ── 2. HSV 双通道 Otsu 二值化 ─────────────────────
|
||||
// 赛道区域 = 白色(255),背景/边界 = 黑色(0)
|
||||
binarizedFrame = image_binerize(resizedFrame);
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
/*
|
||||
* zebra_detect.cpp — 斑马线检测实现
|
||||
* 算法对齐 gd13.py: 透视变换 → 白色掩码 → Sobel 梯度 → 滑窗判定
|
||||
*/
|
||||
#define ZEBRA_DEBUG 1
|
||||
|
||||
#include "zebra_detect.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
#if ZEBRA_DEBUG
|
||||
#define ZLOG(fmt, ...) fprintf(stderr, "[zebra] " fmt "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define ZLOG(fmt, ...) ((void)0)
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// 参数 (对齐 gd13.py)
|
||||
// ============================================================
|
||||
static const int PROC_SIZE = 400;
|
||||
static const int WIN_H = 120;
|
||||
static const int WIN_W = 300;
|
||||
static const int WIN_STEP = 20;
|
||||
|
||||
static const float AMP_THRESH = 12.0f;
|
||||
static const int COUNT_THRESH = 150;
|
||||
static const float MIN_VRATIO = 0.45f;
|
||||
static const int MIN_VPIXELS = 300;
|
||||
|
||||
// ============================================================
|
||||
// 透视变换矩阵
|
||||
// ============================================================
|
||||
static Mat get_birdview_transform()
|
||||
{
|
||||
vector<Point2f> src = {
|
||||
{30.f, 380.f},
|
||||
{370.f, 380.f},
|
||||
{320.f, 180.f},
|
||||
{80.f, 180.f}
|
||||
};
|
||||
vector<Point2f> dst = {
|
||||
{50.f, 350.f},
|
||||
{350.f, 350.f},
|
||||
{350.f, 50.f},
|
||||
{50.f, 50.f}
|
||||
};
|
||||
return getPerspectiveTransform(src, dst);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 白色掩码 (HSV)
|
||||
// ============================================================
|
||||
static Mat get_white_zebra_mask(const Mat &bgr)
|
||||
{
|
||||
Mat hsv;
|
||||
cvtColor(bgr, hsv, COLOR_BGR2HSV);
|
||||
|
||||
Mat white_mask;
|
||||
inRange(hsv, Scalar(0, 0, 180), Scalar(180, 30, 255), white_mask);
|
||||
ZLOG(" 白掩码 H:[0,180] S:[0,30] V:[180,255] 白色像素=%d", countNonZero(white_mask));
|
||||
|
||||
Mat brown_mask;
|
||||
inRange(hsv, Scalar(10, 20, 80), Scalar(30, 100, 200), brown_mask);
|
||||
ZLOG(" 棕掩码 H:[10,30] S:[20,100] V:[80,200] 棕色像素=%d", countNonZero(brown_mask));
|
||||
|
||||
Mat not_brown;
|
||||
bitwise_not(brown_mask, not_brown);
|
||||
bitwise_and(white_mask, not_brown, white_mask);
|
||||
ZLOG(" 排除棕色后白色像素=%d", countNonZero(white_mask));
|
||||
|
||||
Mat kernel = getStructuringElement(MORPH_RECT, Size(2, 2));
|
||||
morphologyEx(white_mask, white_mask, MORPH_OPEN, kernel, Point(-1,-1), 1);
|
||||
morphologyEx(white_mask, white_mask, MORPH_CLOSE, kernel, Point(-1,-1), 1);
|
||||
ZLOG(" 开闭运算后白色像素=%d", countNonZero(white_mask));
|
||||
|
||||
return white_mask;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 预处理
|
||||
// ============================================================
|
||||
static Mat preprocess(const Mat &bgr)
|
||||
{
|
||||
Mat white_mask = get_white_zebra_mask(bgr);
|
||||
Mat gray;
|
||||
cvtColor(bgr, gray, COLOR_BGR2GRAY);
|
||||
bitwise_and(gray, gray, gray, white_mask);
|
||||
|
||||
medianBlur(gray, gray, 3);
|
||||
Mat kernel1 = getStructuringElement(MORPH_RECT, Size(3, 3));
|
||||
Mat kernel2 = getStructuringElement(MORPH_RECT, Size(5, 5));
|
||||
morphologyEx(gray, gray, MORPH_OPEN, kernel1, Point(-1,-1), 1);
|
||||
morphologyEx(gray, gray, MORPH_CLOSE, kernel2, Point(-1,-1), 1);
|
||||
|
||||
return gray;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 梯度计算
|
||||
// ============================================================
|
||||
static void compute_gradient(const Mat &gray, Mat &litude, Mat &theta)
|
||||
{
|
||||
Mat sobelx, sobely;
|
||||
Sobel(gray, sobelx, CV_32F, 1, 0, 3);
|
||||
Sobel(gray, sobely, CV_32F, 0, 1, 3);
|
||||
|
||||
phase(sobelx, sobely, theta, true);
|
||||
for (int r = 0; r < theta.rows; ++r)
|
||||
for (int c = 0; c < theta.cols; ++c)
|
||||
theta.at<float>(r, c) = fmod(theta.at<float>(r, c), 180.f);
|
||||
|
||||
magnitude(sobelx, sobely, amplitude);
|
||||
|
||||
// 统计过滤前
|
||||
int before = countNonZero(amplitude);
|
||||
for (int r = 0; r < amplitude.rows; ++r)
|
||||
for (int c = 0; c < amplitude.cols; ++c)
|
||||
if (amplitude.at<float>(r, c) < 10.f)
|
||||
amplitude.at<float>(r, c) = 0.f;
|
||||
|
||||
int after = countNonZero(amplitude);
|
||||
double amp_min, amp_max;
|
||||
minMaxLoc(amplitude, &_min, &_max);
|
||||
ZLOG(" 梯度: 过滤前=%d, 过滤后(amp>10)=%d, 幅值范围=[%.1f, %.1f]",
|
||||
before, after, amp_min, amp_max);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 单窗口判定 (无 vector 分配, 避免 OOM)
|
||||
// ============================================================
|
||||
static bool is_zebra_window(const Mat &_win, const Mat &ang_win, int win_top)
|
||||
{
|
||||
ZLOG("--- 窗口 top=%d [%dx%d] ---", win_top, amp_win.cols, amp_win.rows);
|
||||
|
||||
int total = 0; // amplitude > AMP_THRESH 的像素总数
|
||||
int hist[4] = {0}; // 水平边缘直方图 [80-85,85-90,90-95,95-100)
|
||||
int v_count = 0; // 垂直边缘计数 [0-10]∪[170-180]
|
||||
|
||||
// 单次遍历: 同时统计 total, hist, v_count
|
||||
for (int r = 0; r < amp_win.rows; ++r)
|
||||
{
|
||||
for (int c = 0; c < amp_win.cols; ++c)
|
||||
{
|
||||
float amp = amp_win.at<float>(r, c);
|
||||
if (amp <= AMP_THRESH)
|
||||
continue;
|
||||
|
||||
++total;
|
||||
float a = ang_win.at<float>(r, c);
|
||||
|
||||
if (a >= 80.f && a < 100.f)
|
||||
{
|
||||
int bin = (int)(a - 80.f) / 5;
|
||||
if (bin >= 0 && bin < 4)
|
||||
hist[bin]++;
|
||||
}
|
||||
|
||||
if (a <= 10.f || a >= 170.f)
|
||||
++v_count;
|
||||
}
|
||||
}
|
||||
|
||||
ZLOG(" 梯度像素(amplitude>%.0f)=%d", AMP_THRESH, total);
|
||||
if (total == 0)
|
||||
{
|
||||
ZLOG(" -> 丢弃: 无有效梯度像素");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 找峰值 bin
|
||||
int peak = 0, peak_bin = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
if (hist[i] > peak) { peak = hist[i]; peak_bin = i; }
|
||||
|
||||
// 二次遍历: 统计峰值方向的像素数 (需要知道具体区间)
|
||||
int h_count = 0;
|
||||
float low_a = 80.f + peak_bin * 5.f;
|
||||
float high_a = low_a + 5.f;
|
||||
for (int r = 0; r < amp_win.rows; ++r)
|
||||
{
|
||||
for (int c = 0; c < amp_win.cols; ++c)
|
||||
{
|
||||
if (amp_win.at<float>(r, c) <= AMP_THRESH)
|
||||
continue;
|
||||
float a = ang_win.at<float>(r, c);
|
||||
if (a >= low_a && a <= high_a)
|
||||
++h_count;
|
||||
}
|
||||
}
|
||||
|
||||
ZLOG(" 水平边缘[80-100): hist=[%d,%d,%d,%d], 峰值bin=%d(%.0f-%.0f度), "
|
||||
"峰值像素=%d, 阈值=%d",
|
||||
hist[0], hist[1], hist[2], hist[3],
|
||||
peak_bin, low_a, high_a, h_count, COUNT_THRESH);
|
||||
|
||||
if (h_count < COUNT_THRESH)
|
||||
{
|
||||
ZLOG(" -> 丢弃: 水平边缘像素不足 (%d < %d)", h_count, COUNT_THRESH);
|
||||
return false;
|
||||
}
|
||||
|
||||
float v_ratio = (float)v_count / (float)(total + 1e-6);
|
||||
ZLOG(" 垂直边缘[0-10|170-180]: 像素=%d, 占比=%.3f, 要求: >=%d && 占比>=%.2f",
|
||||
v_count, v_ratio, MIN_VPIXELS, MIN_VRATIO);
|
||||
|
||||
if (v_count < MIN_VPIXELS)
|
||||
{
|
||||
ZLOG(" -> 丢弃: 垂直像素不足 (%d < %d)", v_count, MIN_VPIXELS);
|
||||
return false;
|
||||
}
|
||||
if (v_ratio < MIN_VRATIO)
|
||||
{
|
||||
ZLOG(" -> 丢弃: 垂直占比不足 (%.3f < %.2f)", v_ratio, MIN_VRATIO);
|
||||
return false;
|
||||
}
|
||||
|
||||
ZLOG(" -> 判定: 斑马线窗口!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// detect_zebra_crossing — 主检测函数
|
||||
// ============================================================
|
||||
ZebraResult detect_zebra_crossing(const Mat &bgr_frame)
|
||||
{
|
||||
ZebraResult result;
|
||||
result.detected = false;
|
||||
result.distance_px = -1;
|
||||
|
||||
ZLOG("========================================");
|
||||
ZLOG("新帧开始: 输入尺寸=%dx%d", bgr_frame.cols, bgr_frame.rows);
|
||||
|
||||
if (bgr_frame.empty())
|
||||
{
|
||||
ZLOG("错误: 输入帧为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
static Mat M = get_birdview_transform();
|
||||
static Mat proc, birdview, roi, gray, amplitude, theta;
|
||||
|
||||
// 1. 缩放 → 透视变换 → 裁剪 ROI(顶部30px) → 恢复 400×400
|
||||
resize(bgr_frame, proc, Size(PROC_SIZE, PROC_SIZE));
|
||||
ZLOG("步骤1-缩放: %dx%d -> %dx%d", bgr_frame.cols, bgr_frame.rows,
|
||||
PROC_SIZE, PROC_SIZE);
|
||||
|
||||
warpPerspective(proc, birdview, M, Size(PROC_SIZE, PROC_SIZE));
|
||||
roi = birdview(Rect(0, 30, PROC_SIZE, PROC_SIZE - 30));
|
||||
resize(roi, proc, Size(PROC_SIZE, PROC_SIZE));
|
||||
ZLOG("步骤1-透视+ROI: 裁剪顶部30px, 鸟瞰输出=%dx%d", proc.cols, proc.rows);
|
||||
|
||||
// 2. 预处理 + 梯度
|
||||
ZLOG("步骤2-预处理开始");
|
||||
gray = preprocess(proc);
|
||||
ZLOG("步骤2-预处理完成, 灰度非零像素=%d", countNonZero(gray));
|
||||
|
||||
ZLOG("步骤3-梯度计算开始");
|
||||
compute_gradient(gray, amplitude, theta);
|
||||
|
||||
int amp_nz = countNonZero(amplitude);
|
||||
ZLOG("步骤3-梯度完成, 总非零梯度像素=%d", amp_nz);
|
||||
|
||||
if (amp_nz == 0)
|
||||
{
|
||||
ZLOG("结果: 无梯度 → 未检测到斑马线");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 3. 滑动窗口
|
||||
ZLOG("步骤4-滑动窗口: 窗口=%dx%d, 步长=%d, 遍历范围=[0,%d]",
|
||||
WIN_W, WIN_H, WIN_STEP, proc.rows - WIN_H);
|
||||
|
||||
int z_ymin = proc.rows;
|
||||
int z_ymax = 0;
|
||||
int valid = 0;
|
||||
int total_wins = 0;
|
||||
|
||||
for (int top = 0; top <= proc.rows - WIN_H; top += WIN_STEP)
|
||||
{
|
||||
++total_wins;
|
||||
Rect win(0, top, WIN_W, WIN_H);
|
||||
if (is_zebra_window(amplitude(win), theta(win), top))
|
||||
{
|
||||
++valid;
|
||||
if (top < z_ymin) z_ymin = top;
|
||||
if (top + WIN_H > z_ymax) z_ymax = top + WIN_H;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 判定
|
||||
int span = z_ymax - z_ymin;
|
||||
ZLOG("步骤5-汇总: 总窗口=%d, 有效=%d, "
|
||||
"斑马线区域: top=%d~%d, 跨度=%d, 判定阈值=%d",
|
||||
total_wins, valid, z_ymin, z_ymax, span, WIN_H);
|
||||
|
||||
if (span > WIN_H)
|
||||
{
|
||||
result.detected = true;
|
||||
result.distance_px = PROC_SIZE - z_ymax;
|
||||
ZLOG("结果: 检测到斑马线! 下沿距底部=%dpx (ymax=%d)",
|
||||
result.distance_px, z_ymax);
|
||||
}
|
||||
else
|
||||
{
|
||||
ZLOG("结果: 未检测到斑马线 (跨度%d <= 阈值%d)", span, WIN_H);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Mild Mega r2 → mild_v13.bin 导出
|
||||
架构: 3→9→9→13→13→19→19→19→44→44→64→64 → head(dw,64→64) → out(64→9)
|
||||
无残差连接, 纯顺序 conv_bn_relu + SE
|
||||
"""
|
||||
import sys, struct, os
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
|
||||
class MildMegaR2(nn.Module):
|
||||
def __init__(self, num_classes=4):
|
||||
super().__init__()
|
||||
channels = [3, 9, 9, 13, 13, 19, 19, 19, 44, 44, 64, 64]
|
||||
strides = [2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1]
|
||||
|
||||
for i in range(11):
|
||||
in_c, out_c, s = channels[i], channels[i + 1], strides[i]
|
||||
if s == 2:
|
||||
conv = nn.Conv2d(in_c, out_c, 3, s, 1, bias=False)
|
||||
elif in_c == out_c:
|
||||
conv = nn.Conv2d(in_c, out_c, 3, 1, 1, groups=in_c, bias=False)
|
||||
else:
|
||||
conv = nn.Conv2d(in_c, out_c, 1, 1, 0, bias=False)
|
||||
|
||||
setattr(self, f'b{i + 1}', nn.Sequential(
|
||||
conv,
|
||||
nn.BatchNorm2d(out_c),
|
||||
nn.ReLU(inplace=True),
|
||||
))
|
||||
|
||||
mid = out_c // 2
|
||||
setattr(self, f'se_{i}', nn.Sequential(
|
||||
nn.AdaptiveAvgPool2d(1),
|
||||
nn.Conv2d(out_c, mid, 1, bias=True),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(mid, out_c, 1, bias=True),
|
||||
nn.Sigmoid(),
|
||||
))
|
||||
|
||||
# head: depthwise 3x3, 64→64
|
||||
self.head = nn.Sequential(
|
||||
nn.Conv2d(64, 64, 3, 1, 1, groups=64, bias=False),
|
||||
nn.BatchNorm2d(64),
|
||||
nn.ReLU(inplace=True),
|
||||
)
|
||||
# output: 1x1 conv, 64→(4 class + 1 bg + 4 bbox) = 9
|
||||
self.out = nn.Conv2d(64, 9, 1, bias=True)
|
||||
|
||||
def forward(self, x):
|
||||
for i in range(11):
|
||||
x = getattr(self, f'b{i + 1}')(x)
|
||||
se_out = getattr(self, f'se_{i}')(x)
|
||||
x = x * se_out
|
||||
x = self.head(x)
|
||||
x = self.out(x)
|
||||
return x
|
||||
|
||||
|
||||
def export(pth_path, bin_path):
|
||||
model = MildMegaR2(num_classes=4)
|
||||
ckpt = torch.load(pth_path, map_location='cpu', weights_only=False)
|
||||
|
||||
# Handle wrapped checkpoint
|
||||
if isinstance(ckpt, dict) and 'model' in ckpt:
|
||||
ckpt = ckpt['model']
|
||||
if isinstance(ckpt, dict) and 'state_dict' in ckpt:
|
||||
ckpt = ckpt['state_dict']
|
||||
|
||||
# Remove 'module.' prefix (from DataParallel/DDP)
|
||||
cleaned = {}
|
||||
for k, v in ckpt.items():
|
||||
nk = k.replace('module.', '')
|
||||
cleaned[nk] = v
|
||||
|
||||
model.load_state_dict(cleaned, strict=False)
|
||||
model.eval()
|
||||
|
||||
params = {}
|
||||
for name, param in model.named_parameters():
|
||||
params[name] = param.detach().cpu().numpy().astype(np.float32)
|
||||
for name, buf in model.named_buffers():
|
||||
params[name] = buf.detach().cpu().numpy().astype(np.float32)
|
||||
|
||||
total = sum(v.size for v in params.values())
|
||||
print(f"Layers: {len(params)}, params: {total:,} ≈ {total / 1000:.1f}K")
|
||||
|
||||
for k, v in sorted(params.items()):
|
||||
sh = str(list(v.shape))
|
||||
print(f" {k:50s} {sh:25s} {v.size:,}")
|
||||
|
||||
with open(bin_path, 'wb') as f:
|
||||
f.write(struct.pack('i', len(params)))
|
||||
for name in sorted(params.keys()):
|
||||
arr = params[name]
|
||||
nb = name.encode('utf-8')
|
||||
f.write(struct.pack('i', len(nb)))
|
||||
f.write(nb)
|
||||
f.write(struct.pack('i', arr.ndim))
|
||||
for d in arr.shape:
|
||||
f.write(struct.pack('i', d))
|
||||
f.write(arr.tobytes())
|
||||
|
||||
size_kb = os.path.getsize(bin_path) / 1024
|
||||
print(f"\nExported: {bin_path} ({size_kb:.0f} KB)")
|
||||
|
||||
# Quick inference test
|
||||
x = torch.randn(1, 3, 120, 160)
|
||||
with torch.no_grad():
|
||||
y = model(x)
|
||||
print(f"Input: {list(x.shape)} → Output: {list(y.shape)} (OK)")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
pth = 'best_v13_mild_r2.pth'
|
||||
if not os.path.exists(pth):
|
||||
print(f"ERROR: {pth} not found")
|
||||
sys.exit(1)
|
||||
export(pth, 'mild_v13.bin')
|
||||
@@ -0,0 +1,18 @@
|
||||
import torch, sys, os
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
pth = 'best_v13_mild_r2.pth'
|
||||
ckpt = torch.load(pth, map_location='cpu', weights_only=False)
|
||||
print(f"Type: {type(ckpt).__name__}")
|
||||
if isinstance(ckpt, dict):
|
||||
if 'model' in ckpt:
|
||||
print("Has 'model' key")
|
||||
ckpt = ckpt['model']
|
||||
if 'state_dict' in ckpt:
|
||||
print("Has 'state_dict' key")
|
||||
ckpt = ckpt['state_dict']
|
||||
print(f"\nTotal keys: {len(ckpt)}")
|
||||
for k, v in sorted(ckpt.items()):
|
||||
shape = tuple(v.shape) if hasattr(v, 'shape') else 'scalar'
|
||||
print(f" {k:50s} {str(shape)}")
|
||||
else:
|
||||
print("Not a dict, top-level keys unknown")
|
||||
Reference in New Issue
Block a user