Compare commits

..

7 Commits

Author SHA1 Message Date
spdis b9141eaf8a fix1: boost恢复为1.6x乘法 2026-07-06 17:13:02 +08:00
spdis e720009794 fix: boost改为固定26 + 删除死代码
- motor_update: boost从乘法改为固定值26
- 删除废弃的font_bitmap.h中文渲染代码(已改用PNG)
- 移除无源码的lcd_test编译目标
- 设备speed=20
2026-07-06 17:11:15 +08:00
spdis 8ee66fe444 {固化}16速45秒两圈 2026-07-06 17:02:10 +08:00
spdis 7b220f5c3b docs: 更新ARCHITECTURE.md参数表与行为文档
- 模型: Mild v12->v13, 阈值更新为{0.80,0.70,0.80,0.85}
- 新增 foresee_lost_scale / sharp_turn_scale 参数
- 移除已删除的 zebra_detect.cpp 引用
- 新增 zebra 冷却期控制策略表(foresee/bang-bang/降速)
- 新增 pedestrian_stop.png LCD 图片显示说明
- 新增模型转换工具 tools/ 引用
2026-07-06 16:56:16 +08:00
spdis 7284a8d037 chore: 删除废弃的zebra_detect(斑马线检测已由Mild模型替代) 2026-07-04 21:17:34 +08:00
spdis 54f13984a9 chore: 从git跟踪中移除.vscode目录(.gitignore) 2026-07-04 21:15:46 +08:00
spdis 670f84131b feat: 添加模型转换工具(Mild v13 PyTorch->bin导出)
- tools/export_mild_v13.py: PyTorch .pth -> 自定义 .bin 导出脚本
- tools/inspect_pth.py: 检查 .pth 权重文件结构
- tools/best_v13_mild_r2.pth: 训练好的 PyTorch 权重
- .gitignore: 追加 build 变体目录和生成文件
2026-07-04 21:15:10 +08:00
18 changed files with 1559 additions and 1735 deletions
+9 -5
View File
@@ -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/
-4
View File
@@ -1,4 +0,0 @@
{
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda"
}
+21 -8
View File
@@ -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
---
+72
View File
@@ -0,0 +1,72 @@
# 比赛参数固化
> 截至 2026-07-0416速,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)
-16
View File
@@ -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);
-3
View File
@@ -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})
-71
View File
@@ -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 — 系统初始化
//
-312
View File
@@ -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 &amplitude, 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, &amp_min, &amp_max);
ZLOG(" 梯度: 过滤前=%d, 过滤后(amp>10)=%d, 幅值范围=[%.1f, %.1f]",
before, after, amp_min, amp_max);
}
// ============================================================
// 单窗口判定 (无 vector 分配, 避免 OOM)
// ============================================================
static bool is_zebra_window(const Mat &amp_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.
+123
View File
@@ -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')
+18
View File
@@ -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")