Initial commit: SmartCar Framework v0.1 — 龙芯2K0300智能车开发框架\n\n- HAL: GPIO/PWM/Encoder/Framebuffer 驱动\n- Control: PID/IMU/Motor/Servo 控制\n- Vision: HSV双Otsu→4点标定IPM→洪泛填充→逐行搜线\n- Strategy: 三区前瞻偏差+速度策略\n- Debug: 文件热调参+LCD预览+cv截帧\n- Scheduler: 5ms timerfd+epoll 中央调度器

This commit is contained in:
2026-05-25 10:31:55 +08:00
commit 28d9c6da58
52 changed files with 2599 additions and 0 deletions

BIN
model/best.pth.1 Normal file

Binary file not shown.

33
model/config.json Normal file
View File

@@ -0,0 +1,33 @@
{
"seed": 42,
"data_path": "preprocessed/dataset_al8.npz",
"save_dir": "checkpoints_heat_v4",
"val_split": 0.15,
"batch_size": 64,
"epochs": 60,
"lr": 0.003,
"weight_decay": 0.0001,
"img_w": 160,
"img_h": 120,
"num_classes": 3,
"out_h": 15,
"out_w": 20,
"stride": 8,
"size_loss_weight": 2.0,
"bg_weight": 0.05,
"resume_from": "checkpoints_heat_v4/best.pth",
"ref_sizes": {
"0": [
75.35844421386719,
62.14570236206055
],
"1": [
49.79265213012695,
38.633811950683594
],
"2": [
67.58988189697266,
34.93575668334961
]
}
}

39
model/deploy_README.md Normal file
View File

@@ -0,0 +1,39 @@
# NanoDetHeat v4 部署说明
## 模型文件
| 文件 | 说明 |
|------|------|
| model_heat_v4.py | 模型架构定义 (PyTorch) |
| best.pth | 训练权重 (141KB, FP32) |
| model_heat_v4.onnx | ONNX 格式模型 |
| config.json | 配置 + 类别参考尺寸 |
## 输入
- 格式: RGB, 160×120, 归一化到 [0,1]
- 预处理: BGR→RGB 翻转, /255.0
## 输出
- 形状: [1, 6, 15, 20]
- 通道 0-3: 类别 logits (红绿灯/锥桶/人行道/背景)
- 通道 4-5: 尺寸预测 (宽/高, 需乘参考尺寸)
## 后处理
1. softmax 通道 0-3 → 概率
2. 对每类找局部峰值 (3×3 邻域抑制)
3. 筛选置信度 > 阈值 (推荐 0.8)
4. 尺寸解码: 实际宽 = 预测宽 × 参考宽
5. 中心坐标: cx=(gx+0.5)×8, cy=(gy+0.5)×8
## 类别参考尺寸 (像素, 160×120)
红绿灯: 73.7 × 60.9
锥桶: 49.4 × 38.6
人行道: 71.2 × 36.3
## 推荐阈值
th=0.8: 精确度 62.9%, 召回 88.7%

13
model/model.cpp Normal file
View File

@@ -0,0 +1,13 @@
#include "model.hpp"
bool model_init()
{
// TODO: 加载 TFLite 模型,初始化解释器
return false;
}
void model_infer(const uint8* image, int w, int h, float* output, int num_classes)
{
// TODO: 运行推理,填充 output[0..num_classes-1]
for (int i = 0; i < num_classes; ++i) output[i] = 0.0f;
}

11
model/model.hpp Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include "types.hpp"
// TFLM 模型推理接口 — 占位实现,后续接入模型
// 用法:
// 1. 将 .tflite 模型转为 C header 数组放入 model/ 目录
// 2. 实现 model_init() / model_infer()
// 3. 在 element.cpp 中调用
bool model_init();
void model_infer(const uint8* image, int w, int h, float* output, int num_classes);

BIN
model/model_heat_v4.onnx Normal file

Binary file not shown.

100
model/model_heat_v4.py Normal file
View File

@@ -0,0 +1,100 @@
"""
NanoDetHeat v4: 残差 + SE注意力 + 扩通道
MACs: ~8.7M, 参数: ~18K, 目标: 20 TPS @ 0.44 GOPS (79%)
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
def conv_bn_relu(in_c, out_c, kernel, stride=1, groups=1):
pad = kernel // 2
return nn.Sequential(
nn.Conv2d(in_c, out_c, kernel, stride, pad, groups=groups, bias=False),
nn.BatchNorm2d(out_c),
nn.ReLU(inplace=True),
)
class SEResDWBlock(nn.Module):
"""残差DW + SE通道注意力"""
def __init__(self, in_c, out_c, stride=1, reduction=4):
super().__init__()
self.dw = conv_bn_relu(in_c, in_c, 3, stride=stride, groups=in_c)
self.pw = conv_bn_relu(in_c, out_c, 1)
self.skip = nn.Identity() if (stride == 1 and in_c == out_c) else nn.Sequential(
nn.Conv2d(in_c, out_c, 1, stride, bias=False),
nn.BatchNorm2d(out_c),
)
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(out_c, out_c // reduction, 1),
nn.ReLU(inplace=True),
nn.Conv2d(out_c // reduction, out_c, 1),
nn.Sigmoid(),
)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
out = self.pw(self.dw(x))
out = out + self.skip(x)
return self.relu(out * self.se(out))
class NanoDetHeatV4(nn.Module):
def __init__(self, num_classes=3):
super().__init__()
self.stem = conv_bn_relu(3, 8, 3, stride=2)
self.block1 = SEResDWBlock(8, 16, stride=1)
self.block2 = SEResDWBlock(16, 28, stride=2)
self.block3 = SEResDWBlock(28, 40, stride=1)
self.block4 = SEResDWBlock(40, 56, stride=2)
self.block5 = SEResDWBlock(56, 64, stride=1)
self.shared = conv_bn_relu(64, 24, 1)
self.cls_head = nn.Conv2d(24, num_classes + 1, 1, bias=True)
self.size_head = nn.Conv2d(24, 2, 1, bias=True)
self._init_weights()
def _init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None: nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1); nn.init.constant_(m.bias, 0)
self.cls_head.bias.data[-1] = 0.5 # 轻微背景偏置,焦点损失自动调整
def forward(self, x):
x = self.stem(x); x = self.block1(x); x = self.block2(x)
x = self.block3(x); x = self.block4(x); x = self.block5(x)
x = self.shared(x)
return torch.cat([self.cls_head(x), self.size_head(x)], dim=1)
OUT_H, OUT_W = 15, 20
STRIDE = 8
if __name__ == "__main__":
m = NanoDetHeatV4()
p = sum(p.numel() for p in m.parameters())
macs = (3*3*3*8*80*60 + # stem
8*9*80*60 + 8*16*80*60 + 8*16*80*60 + # b1
16*9*40*30 + 16*28*40*30 + 16*28*40*30 + # b2
28*9*40*30 + 28*40*40*30 + # b3
40*9*20*15 + 40*56*20*15 + 40*56*20*15 + # b4
56*9*20*15 + 56*64*20*15 + # b5
64*24*20*15 + 24*4*20*15 + 24*2*20*15) # heads
print(f"Params: {p:,} = ~{p/1000:.1f}K")
print(f"MACs: {macs:,} = ~{macs/1e6:.2f}M")
print(f"20 TPS: {macs*20*2/1e9:.2f} GOPS ({(macs*20*2/0.44e9)*100:.0f}%)")
print(f"25 TPS: {macs*25*2/1e9:.2f} GOPS ({(macs*25*2/0.44e9)*100:.0f}%)")
x = torch.randn(1,3,120,160); y = m(x)
print(f"Output: {y.shape}")