清理编码器+死代码+可读性整理: 删除ENCODER类(含UB析构), MotorController简化为PWM+GPIO裸控制器, 移除mortor_kp/ki/kd全局变量, 排除vl53l0x/zebra_detect死代码, 修复image_cv行列写反, 清理无关include

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
spdis
2026-06-10 15:15:34 +08:00
parent d284e18407
commit 114d93cec7
16 changed files with 264 additions and 519 deletions
+14 -29
View File
@@ -6,45 +6,30 @@ cmake_minimum_required(VERSION 3.5.0)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -pthread -Wall -march=loongarch64 -mtune=loongarch64 -ffast-math -funroll-loops -fomit-frame-pointer") # 对于 C++ 编译器
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wall") # 对于 C 编译器
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -pthread -Wall -march=loongarch64 -mtune=loongarch64 -ffast-math -funroll-loops -fomit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wall")
# 定义项目名称和版本,并指定使用C和C++语言
# 定义项目名称和版本
project(smartcar_demo2 VERSION 0.1.0 LANGUAGES C CXX)
# 设置OpenCV的安装路径
# OpenCV 设备端路径
set(OpenCV_DIR /mnt/d/PPPProgram/smartcar/opencv_device/lib/cmake/opencv4)
# 查找OpenCV库,确保安装了所需的依赖
find_package(OpenCV REQUIRED)
# 包含OpenCV的头文件路径
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(src)
message(STATUS "OpenCV Include Directories: ${OpenCV_INCLUDE_DIRS}")
# 包含项目的自定义库路径
# 项目头文件路径
include_directories(src)
include_directories(lib)
# 查找源文件所在的目录
# 收集 src 下所有 .cpp (自动发现)
aux_source_directory(src DIR_SRCS)
# 将 src 目录下的源文件编译为静态库
add_library(common_lib STATIC ${DIR_SRCS})
# 排除暂不使用的模块(源文件保留)
# vl53l0x.cpp — 激光测距, 硬件未接
# zebra_detect.cpp — 经典斑马线检测, 已由 nanodet 模型替代
list(FILTER DIR_SRCS EXCLUDE REGEX "(vl53l0x\\.cpp|zebra_detect\\.cpp)")
# 添加子目录
add_subdirectory(main) # 主程序
add_subdirectory(demo1) # demo1
add_subdirectory(framebuffer_demo)
add_subdirectory(opencv_demo1)
add_subdirectory(opencv_demo2)
add_subdirectory(opencv_demo3)
add_subdirectory(encoder_demo)
add_subdirectory(jy62_demo)
add_subdirectory(key_demo)
add_subdirectory(wonderEcho_demo)
add_subdirectory(udp_receive)
add_subdirectory(image_test)
# add_subdirectory(zebra_demo)
add_subdirectory(gd13_demo)
add_subdirectory(screenshot_demo)
# 静态库 + 主程序
add_library(common_lib STATIC ${DIR_SRCS})
add_subdirectory(main)
+7 -18
View File
@@ -1,37 +1,26 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2024-10-10 14:36:47
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-21 10:42:00
* @FilePath: /smartcar/lib/MotorController.h
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* MotorController — 直流电机控制器
*
* 封装 PWM 调速 + GPIO 方向控制,开环占空比直驱。
* 不包含编码器反馈和 PID 闭环(开环巡线够用)。
*/
#ifndef MOTOR_CONTROLLER_H
#define MOTOR_CONTROLLER_H
#include "PwmController.h"
#include "PIDController.h"
#include "GPIO.h"
#include "encoder.h"
class MotorController
{
public:
MotorController(int pwmchip, int pwmnum, int gpioNum, unsigned int period_ns,
double kp, double ki, double kd, double targetSpeed,
int encoder_pwmNum, int encoder_gpioNum, int encoder_dir_);
MotorController(int pwmchip, int pwmnum, int gpioNum, unsigned int period_ns);
~MotorController(void);
void updateSpeed(void);
void updateTarget(int speed);
void updateduty(double dutyCycle);
PIDController pidController;
void updateduty(double dutyCycle); // dutyCycle: -100.0 ~ 100.0
private:
PwmController pwmController;
ENCODER encoder;
GPIO directionGPIO;
int encoder_dir;
};
#endif // MOTOR_CONTROLLER_H
#endif
-6
View File
@@ -17,20 +17,14 @@
#include <thread>
#include "image_cv.h"
#include "PIDController.h"
#include "PwmController.h"
#include "global.h"
#include "frame_buffer.h"
#include "serial.h"
#include "control.h"
int CameraInit(uint8_t camera_id, double dest_fps, int width, int height);
int CameraHandler(void);
void cameraDeInit(void);
extern double kp;
extern double ki;
extern double kd;
extern double g_steer_deviation;
#endif
+6 -9
View File
@@ -1,21 +1,18 @@
#ifndef CONTROL_H
#define CONTROL_H
#include <unistd.h>
#include "MotorController.h"
#include "global.h"
#include "serial.h"
#include "GPIO.h"
// ── 电机控制接口 ──
void ControlInit();
void ControlUpdate(double speed, bool zebra_block);
void ControlExit();
extern double mortor_kp;
extern double mortor_ki;
extern double mortor_kd;
extern GPIO mortorEN;
// 左右电机对象(control.cpp 分配)
extern MotorController *motorController[2];
// 电机使能 GPIO (73)
extern GPIO mortorEN;
#endif
-75
View File
@@ -1,75 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2024-10-11 06:20:04
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2024-12-01 03:48:28
* @FilePath: /smartcar/lib/encoder.h
* @Description:
*
* Copyright (c) 2024 by ilikara 3435193369@qq.com, All Rights Reserved.
*/
#ifndef ENCODER_H
#define ENCODER_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include "GPIO.h"
#define PWM_BASE_ADDR 0x1611B000
#define PWM_OFFSET 0x10
#define LOW_BUFFER_OFFSET 0x4
#define FULL_BUFFER_OFFSET 0x8
#define CONTROL_REG_OFFSET 0xC
#define CNTR_ENABLE_BIT (1 << 0) // 计数器使能
#define PULSE_OUT_ENABLE_BIT (1 << 3) // 脉冲输出使能(低有效)
#define SINGLE_PULSE_BIT (1 << 4) // 单脉冲控制位
#define INT_ENABLE_BIT (1 << 5) // 中断使能
#define INT_STATUS_BIT (1 << 6) // 中断状态
#define COUNTER_RESET_BIT (1 << 7) // 计数器重置
#define MEASURE_PULSE_BIT (1 << 8) // 测量脉冲使能
#define INVERT_OUTPUT_BIT (1 << 9) // 输出翻转使能
#define DEAD_ZONE_ENABLE_BIT (1 << 10) // 防死区使能
#define LOW_BUFFER_ADDR (PWM_BASE_ADDR + LOW_BUFFER_OFFSET)
#define FULL_BUFFER_ADDR (PWM_BASE_ADDR + FULL_BUFFER_OFFSET)
#define CONTROL_REG_ADDR (PWM_BASE_ADDR + CONTROL_REG_OFFSET)
#define GPIO_PIN 73
#define GPIO_PATH "/sys/class/gpio/gpio73/value"
#define PAGE_SIZE 0x10000
#define REG_READ(addr) (*(volatile uint32_t *)(addr))
#define REG_WRITE(addr, val) (*(volatile uint32_t *)(addr) = (val))
class ENCODER
{
public:
ENCODER(int pwmNum, int gpioNum);
~ENCODER();
double pulse_counter_update(void);
private:
uint32_t base_addr;
GPIO directionGPIO;
void *low_buffer;
void *full_buffer;
void *control_buffer;
void *map_register(uint32_t physical_address, size_t size);
void PWM_Init(void);
void reset_counter(void);
};
#endif
+17 -12
View File
@@ -2,18 +2,12 @@
#define GLOBAL_H
#include <string>
#include <iostream>
#include <fstream>
#include <atomic>
// ── 参数文件名 ──
const std::string kp_file = "./kp";
const std::string ki_file = "./ki";
const std::string kd_file = "./kd";
const std::string mortor_kp_file = "./mortor_kp";
const std::string mortor_ki_file = "./mortor_ki";
const std::string mortor_kd_file = "./mortor_kd";
const std::string start_file = "./start";
const std::string showImg_file = "./showImg";
const std::string destfps_file = "./destfps";
@@ -24,14 +18,25 @@ const std::string speed_file = "./speed";
const std::string deadband_file = "./deadband";
const std::string steer_gain_file = "./steer_gain";
const std::string center_bias_file = "./center_bias";
const std::string debug_file = "./debug";
// 从文件读取双精度值
double readDoubleFromFile(const std::string &filename);
bool readFlag(const std::string &filename);
void cfg_load_all();
// 从文件中读取标志
bool readFlag(const std::string &filename);
extern std::atomic<double> PID_rotate;
// ── 启动时缓存的运行参数 ──
struct CfgCache {
double speed = 60; // 目标速度(占空比 %)
double foresee = 80; // 前瞻行
double zebrasee = 60; // 斑马线触发距离阈值
double deadband = 5; // 舵机死区
double steer_gain = 1.0; // 舵机增益
double center_bias = 0; // 中线偏移修正
int start = 0; // 1=启动 0=停止
int showImg = 0; // LCD 预览开关
int debug = 0; // debug 模式
};
extern CfgCache g_cfg;
extern double target_speed;
+4 -16
View File
@@ -1,11 +1,3 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2025-01-04 06:51:37
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-13 08:05:00
* @FilePath: /smartcar/lib/image_main.h
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
@@ -13,16 +5,12 @@
void image_main();
extern cv::Mat raw_frame;
extern cv::Mat grayFrame;
extern cv::Mat binarizedFrame;
extern cv::Mat morphologyExFrame;
extern cv::Mat track;
extern std::vector<int> left_line; // 左边缘列号数组
extern std::vector<int> right_line; // 右边缘列号数组
extern std::vector<int> mid_line; // 中线列号数组
extern std::vector<double> left_line_filtered; // 中线列号数组
extern std::vector<double> right_line_filtered; // 中线列号数组
extern std::vector<double> mid_line_filtered; // 中线列号数组
extern std::vector<int> left_line;
extern std::vector<int> right_line;
extern std::vector<int> mid_line;
extern int line_tracking_height, line_tracking_width;
extern int line_tracking_height, line_tracking_width;
+9
View File
@@ -1,4 +1,13 @@
# 主程序
add_executable(smartcar_demo1 main.cpp)
# 暂时排除激光测距(未使用),保留源文件
set(EXCLUDE_VL53L0X vl53l0x.cpp)
# 排除死代码:斑马线传统检测(已由 nanodet 模型替代)、编码器采集(开环控制不需要)
set(EXCLUDE_DEAD zebra_detect.cpp encoder.cpp)
# 从静态库源文件列表中移除
list(FILTER DIR_SRCS EXCLUDE REGEX "(${EXCLUDE_VL53L0X}|${EXCLUDE_DEAD})")
target_link_libraries(smartcar_demo1 common_lib ${OpenCV_LIBS})
+11 -13
View File
@@ -24,31 +24,29 @@ int main(void)
double dest_fps = readDoubleFromFile(destfps_file);
if (dest_fps <= 0) dest_fps = 30.0;
cfg_load_all();
if (CameraInit(0, dest_fps, 320, 240) < 0) {
std::cerr << "CameraInit failed" << std::endl;
return -1;
}
ControlInit();
std::cout << "All services started (single-thread mode)" << std::endl;
std::cout << "All services started" << std::endl;
int tick = 0;
while (running.load())
{
if (CameraHandler() < 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
CameraHandler();
if (++tick % 7 == 0) {
g_cfg.debug = readFlag(debug_file);
}
if (++tick % 15 == 0)
{
target_speed = readDoubleFromFile(speed_file);
mortor_kp = readDoubleFromFile(mortor_kp_file);
mortor_ki = readDoubleFromFile(mortor_ki_file);
mortor_kd = readDoubleFromFile(mortor_kd_file);
kp = readDoubleFromFile(kp_file);
ki = readDoubleFromFile(ki_file);
kd = readDoubleFromFile(kd_file);
if (g_cfg.debug) {
cfg_load_all();
}
target_speed = g_cfg.speed;
}
std::cout << "Stopping..." << std::endl;
+7 -43
View File
@@ -1,22 +1,14 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2024-10-10 14:36:42
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2025-03-21 10:41:17
* @FilePath: /smartcar/src/MotorController.cpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* MotorController — 直流电机控制器实现
*/
#include "MotorController.h"
MotorController::MotorController(int pwmchip, int pwmnum, int gpioNum, unsigned int period_ns,
double kp, double ki, double kd, double targetSpeed,
int encoder_pwmNum, int encoder_gpioNum, int encoder_dir_)
: pwmController(pwmchip, pwmnum), directionGPIO(gpioNum), pidController(kp, ki, kd, targetSpeed, INCREMENTAL, 80),
encoder(encoder_pwmNum, encoder_gpioNum), encoder_dir(encoder_dir_)
MotorController::MotorController(int pwmchip, int pwmnum, int gpioNum, unsigned int period_ns)
: pwmController(pwmchip, pwmnum), directionGPIO(gpioNum)
{
pwmController.setPeriod(period_ns); // 设置 PWM 周期
pwmController.setPeriod(period_ns);
directionGPIO.setDirection("out");
pwmController.enable(); // 启用 PWM
pwmController.enable();
}
MotorController::~MotorController(void)
@@ -26,37 +18,9 @@ MotorController::~MotorController(void)
void MotorController::updateduty(double dutyCycle)
{
int newduty = pwmController.readPeriod() * abs(dutyCycle) / 100.0;
int newduty = pwmController.readPeriod() * std::abs(dutyCycle) / 100.0;
if (newduty != pwmController.readDutyCycle())
{
pwmController.setDutyCycle(newduty);
}
// 根据 PID 输出设置 GPIO 的方向
if (dutyCycle > 0)
{
directionGPIO.setValue(1); // 正向
}
else
{
directionGPIO.setValue(0); // 反向
}
//std::cout << encoder.pulse_counter_update() << std::endl;
}
void MotorController::updateSpeed(void)
{
double encoderReading = encoder.pulse_counter_update() * encoder_dir;
// std::cout << encoderReading << std::endl;
double output = pidController.update(encoderReading);
// int dutyCycle = static_cast<int>(output);
// 设置 PWM 占空比
updateduty(output);
std::cout << encoderReading << " " << output << std::endl;
}
void MotorController::updateTarget(int speed)
{
pidController.setTarget(speed);
directionGPIO.setValue(dutyCycle > 0);
}
+101 -104
View File
@@ -4,12 +4,15 @@
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
cv::VideoCapture cap;
cv::Mat raw_mat;
cv::Mat decoded_frame; // 1/4 解码输出复用 buffer
static int g_decode_mode = -1; // -1=未检测 0=全BGR回退 1=原始JPEG缩放解码
double kp = 0, ki = 0, kd = 0;
int screenWidth, screenHeight, newWidth, newHeight;
int fb;
uint16_t *fb_buffer;
@@ -21,12 +24,11 @@ PwmController servo(1, 0);
#define ZEBRA_CLASS 3
static float g_thresh[4] = {0.80f, 0.80f, 0.80f, 0.75f};
// ── 斑马线去抖: 远处→近处接近逻辑, 防反光误触发 ──
#define ZEBRA_MIN_FRAMES 5 // 累计检测至少5帧
#define ZEBRA_FAR_CY 50 // 必须在cy≤50处出现过(远处)
static int g_zc_frames = 0; // 当前接近episode中检测帧数
static int g_zc_min_cy = 120; // 当前episode中最小cy(最远)
// ── 斑马线去抖 ──
#define ZEBRA_MIN_FRAMES 5
#define ZEBRA_FAR_CY 50
static int g_zc_frames = 0;
static int g_zc_min_cy = 120;
// ── 斑马线状态机 ──
enum ZState { Z_NORMAL, Z_STOP, Z_COOLDOWN };
@@ -40,26 +42,8 @@ static int g_box_count = 0;
static bool g_lcd_on = true;
double g_steer_deviation = 0;
PIDController ServoControl(1.0, 0.0, 2.0, 0.0, POSITION, 1250000);
// ── 背景采集线程 (只做 cap.read, 不参与控制) ──
static std::mutex frameMutex;
static cv::Mat pubframe;
static bool captureRunning;
static std::thread captureWorker;
void streamCapture(void)
{
cv::Mat tmp;
while (captureRunning) {
cap.read(tmp);
frameMutex.lock();
pubframe = tmp;
frameMutex.unlock();
}
}
// ── I2C 音频 (持久打开) ──
// ── I2C 音频 ──
static int i2c_audio_fd = -1;
static bool i2c_audio_open()
@@ -100,17 +84,14 @@ int CameraInit(uint8_t camera_id, double dest_fps, int width, int height)
cap.open(0, cv::CAP_V4L2);
if (!cap.isOpened()) cap.open(0);
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240);
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); // 后端直接返回原始 MJPEG 字节流, 由我们做 1/4 解码
if (!cap.isOpened()) {
printf("无法打开摄像头\n");
munmap(fb_buffer, fb_size); close(fb); return -1;
}
cap.set(cv::CAP_PROP_FRAME_WIDTH, width);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, height);
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
cap.set(cv::CAP_PROP_AUTO_EXPOSURE, -1);
int cameraWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int cameraHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
@@ -137,23 +118,12 @@ int CameraInit(uint8_t camera_id, double dest_fps, int width, int height)
i2c_audio_open();
captureRunning = true;
captureWorker = std::thread(streamCapture);
// 等待第一帧就绪
for (int i = 0; i < 60 && pubframe.empty(); ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
if (pubframe.empty()) { printf("警告: 摄像头首帧超时\n"); }
return static_cast<int>(1000.0 / std::min(fps, dest_fps));
}
void cameraDeInit(void)
{
captureRunning = false;
cap.release();
if (captureWorker.joinable()) captureWorker.join();
struct fb_var_screeninfo vinfo;
if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) != -1) {
size_t fb_size = vinfo.yres_virtual * vinfo.xres_virtual * vinfo.bits_per_pixel / 8;
@@ -180,8 +150,7 @@ static void play_zebra_audio()
printf("[ZEBRA] 语音失败: I2C 未打开\n");
return;
}
ioctl(i2c_audio_fd, I2C_SLAVE, 0x34); // 每次重设从地址
ioctl(i2c_audio_fd, I2C_SLAVE, 0x34);
union i2c_smbus_data d;
struct i2c_smbus_ioctl_data a;
@@ -198,40 +167,66 @@ static void play_zebra_audio()
printf("[ZEBRA] 语音播报已触发\n");
}
// ── LCD Mats 预分配 (避免每帧 new/delete) ──
static cv::Mat lcd_fbImage;
static cv::Mat lcd_resized;
static cv::Mat lcd_colored;
int CameraHandler(void)
{
// ── 1. 取最新帧 (背景线程持续采集, 写全局 raw_frame 供 image_main 使用) ──
frameMutex.lock();
raw_frame = pubframe;
frameMutex.unlock();
if (raw_frame.empty()) { return -1; }
struct timespec t0,t1,t2,t3,t4,t5;
// ── 2. 保存图像 ──
if (readFlag(saveImg_file)) {
if (saveCameraImage(raw_frame, "./image"))
printf("图像%d已保存\n", saved_frame_count);
// ── 1. 取帧 ──
clock_gettime(CLOCK_MONOTONIC,&t0);
cap.read(raw_mat);
clock_gettime(CLOCK_MONOTONIC,&t1);
if (raw_mat.empty()) return -1;
// CONVERT_RGB=0 检测: 单通道=原始JPEG字节流 → 1/4解码; 三通道=后端已解码 → 回退
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解码160x120" : "未生效->全解码回退640x480",
g_decode_mode);
}
// ── 3. 视觉巡线 (禁止动) ──
image_main();
if (g_decode_mode == 1) {
cv::imdecode(raw_mat, cv::IMREAD_REDUCED_COLOR_4, &decoded_frame);
if (decoded_frame.empty()) return -1;
raw_frame = decoded_frame;
} else {
raw_frame = raw_mat;
}
// ── 4. 模型推理 (每2帧一次) ──
// ── 2. 保存图像 ──
if (g_cfg.debug) {
if (readFlag(saveImg_file)) {
if (saveCameraImage(raw_frame, "./image"))
printf("图像%d已保存\n", saved_frame_count);
}
}
// ── 3. 视觉巡线 ──
image_main();
clock_gettime(CLOCK_MONOTONIC,&t2);
// ── 4. 模型推理 (每2帧一次, 640×480直入) ──
static int infer_skip = 0;
if (++infer_skip >= 2) {
infer_skip = 0;
g_box_count = 0;
if (model_v10_ready()) {
cv::Mat mInput;
cv::resize(raw_frame, mInput, cv::Size(160, 120), 0, 0, cv::INTER_AREA);
g_box_count = model_v10_detect(mInput.data, 160, 120, g_boxes, 16, g_thresh);
g_box_count = model_v10_detect(raw_frame.data, raw_frame.cols, raw_frame.rows,
g_boxes, 16, g_thresh);
}
}
clock_gettime(CLOCK_MONOTONIC,&t3);
// ── 5. 斑马线去抖+停/走状态机 ──
bool zebra_near = false;
bool zebra_seen = false;
int zebra_cy = 0;
float zebra_cf = 0;
// ── 5. 斑马线去抖+状态机 ──
bool zebra_near = false;
bool zebra_seen = false;
int zebra_cy = 0;
float zebra_cf = 0;
g_zebra_ever = false;
for (int i = 0; i < g_box_count; ++i) {
@@ -245,8 +240,7 @@ int CameraHandler(void)
}
zebra_seen = true;
int foresee = (int)readDoubleFromFile(zebrasee_file);
if (zebra_cy > foresee)
if (zebra_cy > g_cfg.zebrasee)
zebra_near = true;
break;
}
@@ -266,11 +260,11 @@ int CameraHandler(void)
if (enough && from_far) {
play_zebra_audio();
g_zstate = Z_STOP; g_ztime = now;
printf("[ZEBRA] cy=%d cf=%.2f f=%d mc=%d 停车4s 冷却5s\n",
zebra_cy, zebra_cf, g_zc_frames, g_zc_min_cy);
if (g_cfg.debug)
printf("[ZEBRA] cy=%d cf=%.2f 停车4s 冷却5s\n", zebra_cy, zebra_cf);
g_zc_frames = 0; g_zc_min_cy = 120;
} else {
printf("[ZEBRA] cy=%d 拒绝: f=%d/%d mc=%d/%d\n",
} else if (g_cfg.debug) {
printf("[ZEBRA] cy=%d 拒绝 f=%d/%d mc=%d/%d\n",
zebra_cy, g_zc_frames, ZEBRA_MIN_FRAMES, g_zc_min_cy, ZEBRA_FAR_CY);
}
}
@@ -278,33 +272,30 @@ int CameraHandler(void)
case Z_STOP:
if (now - g_ztime >= 4) {
g_zstate = Z_COOLDOWN; g_ztime = now;
printf("[ZEBRA] 起步\n");
if (g_cfg.debug) printf("[ZEBRA] 起步\n");
}
break;
case Z_COOLDOWN:
if (now - g_ztime >= 5) {
g_zstate = Z_NORMAL;
printf("[ZEBRA] 恢复\n");
if (g_cfg.debug) printf("[ZEBRA] 恢复\n");
}
break;
}
// ── 6. 舵机 ──
if (readFlag(start_file)) {
int foresee = (int)readDoubleFromFile(foresee_file);
if (g_cfg.start) {
int foresee = (int)g_cfg.foresee;
int check_row = foresee / calc_scale;
if (check_row >= 0 && check_row < line_tracking_height && mid_line[check_row] != 255) {
double deviation = mid_line[check_row] * calc_scale - newWidth / 2;
double bias = readDoubleFromFile(center_bias_file);
deviation -= bias;
deviation -= g_cfg.center_bias;
g_steer_deviation = deviation / (newWidth / 2.0);
double deadband = readDoubleFromFile(deadband_file);
if (std::abs(deviation) < deadband) {
if (std::abs(deviation) < g_cfg.deadband) {
servo.setDutyCycle(1500000);
} else {
double steer_gain = readDoubleFromFile(steer_gain_file);
double norm = deviation / (newWidth / 2.0);
double offset = norm * steer_gain * 300000;
double offset = norm * g_cfg.steer_gain * 300000;
double duty_ns = 1500000.0 + offset;
duty_ns = std::clamp(duty_ns, 1200000.0, 1800000.0);
servo.setDutyCycle(static_cast<unsigned int>(duty_ns));
@@ -312,32 +303,32 @@ int CameraHandler(void)
}
}
// ── 7. 电机控制 (单出口) ──
// ── 7. 电机控制 ──
ControlUpdate(target_speed, g_zstate == Z_STOP);
clock_gettime(CLOCK_MONOTONIC,&t4);
// ── 8. LCD ──
{
static int lcd_check = 0;
if (--lcd_check < 0) { g_lcd_on = readFlag(showImg_file); lcd_check = 10; }
static int lcd_check = 0;
if (--lcd_check < 0) {
g_lcd_on = g_cfg.showImg;
lcd_check = 10;
}
if (g_lcd_on) {
cv::Mat fbImage(screenHeight, screenWidth, CV_8UC3, cv::Scalar(0, 0, 0));
cv::Mat resizedFrame;
cv::resize(track, resizedFrame, cv::Size(newWidth, newHeight));
cv::Mat coloredResizedFrame;
cv::cvtColor(resizedFrame, coloredResizedFrame, cv::COLOR_GRAY2BGR);
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);
fbImage.setTo(cv::Scalar(0, 0, 0));
cv::Rect roi((screenWidth - newWidth) / 2, (screenHeight - newHeight) / 2, newWidth, newHeight);
coloredResizedFrame.copyTo(fbImage(roi));
lcd_colored.copyTo(lcd_fbImage(roi));
for (int y = 0; y < line_tracking_height; y++) {
int sLX=static_cast<int>(left_line[y]*calc_scale), sRX=static_cast<int>(right_line[y]*calc_scale);
int sMX=static_cast<int>(mid_line[y]*calc_scale), sY=static_cast<int>(y*calc_scale);
cv::line(fbImage(roi), cv::Point(sLX,sY), cv::Point(sLX,sY), cv::Scalar(0,0,255), calc_scale);
cv::line(fbImage(roi), cv::Point(sRX,sY), cv::Point(sRX,sY), cv::Scalar(0,255,0), calc_scale);
cv::line(fbImage(roi), cv::Point(sMX,sY), cv::Point(sMX,sY), cv::Scalar(255,0,0), calc_scale);
cv::line(lcd_fbImage(roi), cv::Point(sLX,sY), cv::Point(sLX,sY), cv::Scalar(0,0,255), calc_scale);
cv::line(lcd_fbImage(roi), cv::Point(sRX,sY), cv::Point(sRX,sY), cv::Scalar(0,255,0), calc_scale);
cv::line(lcd_fbImage(roi), cv::Point(sMX,sY), cv::Point(sMX,sY), cv::Scalar(255,0,0), calc_scale);
}
float bx=(float)newWidth/160.0f, by=(float)newHeight/120.0f;
@@ -349,26 +340,32 @@ int CameraHandler(void)
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 == ZEBRA_CLASS) color=cv::Scalar(255,0,255);
cv::rectangle(fbImage(roi), cv::Point(x1,y1), cv::Point(x2,y2), color, 2);
cv::rectangle(lcd_fbImage(roi), cv::Point(x1,y1), cv::Point(x2,y2), color, 2);
char lab[16]; std::snprintf(lab,16,"%d %.0f",g_boxes[i].cls,g_boxes[i].conf*100);
cv::putText(fbImage(roi), lab, cv::Point(x1+2,y1+10), cv::FONT_HERSHEY_SIMPLEX,0.3,color,1);
cv::putText(lcd_fbImage(roi), lab, cv::Point(x1+2,y1+10), cv::FONT_HERSHEY_SIMPLEX,0.3,color,1);
}
const char* ztxt="N";
if (g_zstate==Z_STOP) ztxt="S";
else if (g_zstate==Z_COOLDOWN) ztxt="C";
cv::putText(fbImage(roi), ztxt, cv::Point(2,newHeight-4), cv::FONT_HERSHEY_SIMPLEX,0.4,cv::Scalar(0,255,255),1);
cv::putText(lcd_fbImage(roi), ztxt, cv::Point(2,newHeight-4), cv::FONT_HERSHEY_SIMPLEX,0.4,cv::Scalar(0,255,255),1);
convertMatToRGB565(fbImage, fb_buffer, screenWidth, screenHeight);
convertMatToRGB565(lcd_fbImage, fb_buffer, screenWidth, screenHeight);
}
// ── 9. FPS ──
// ── 9. FPS + 分步计时 ──
{
static int fc=0; static timespec t0; if(fc==0) clock_gettime(CLOCK_MONOTONIC,&t0);
clock_gettime(CLOCK_MONOTONIC,&t5);
static int fc=0; static timespec tf0; if(fc==0) clock_gettime(CLOCK_MONOTONIC,&tf0);
fc++;
if(fc%15==0){ timespec t1; clock_gettime(CLOCK_MONOTONIC,&t1);
double dt=(t1.tv_sec-t0.tv_sec)+(t1.tv_nsec-t0.tv_nsec)*1e-9;
printf("fps=%.1f zc=%c lcd=%c \r", fc/dt, g_zebra_ever?'Y':' ', g_lcd_on?'Y':' ');
if(fc%15==0){ 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_r =(t1.tv_sec-t0.tv_sec)*1000.0+(t1.tv_nsec-t0.tv_nsec)*1e-6;
double ms_vis=(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_nsec-t1.tv_nsec)*1e-6;
double ms_mdl=(t3.tv_sec-t2.tv_sec)*1000.0+(t3.tv_nsec-t2.tv_nsec)*1e-6;
double ms_ctl=(t4.tv_sec-t3.tv_sec)*1000.0+(t4.tv_nsec-t3.tv_nsec)*1e-6;
printf("fps=%.1f|rd=%.0f vi=%.0f md=%.0f ct=%.0f ms\r",
fc/dt, ms_r, ms_vis, ms_mdl, ms_ctl);
fflush(stdout);
}
}
+16 -19
View File
@@ -1,54 +1,51 @@
/*
* control — 电机控制层
*
* 开环占空比控制:根据目标速度和偏差计算占空比,直接输出到 PWM。
* 无编码器反馈,无 PID 闭环。
*/
#include "control.h"
#include "GPIO.h"
#include "global.h"
extern double g_steer_deviation;
MotorController *motorController[2] = {nullptr, nullptr};
GPIO mortorEN(73);
double mortor_kp = 1000;
double mortor_ki = 300;
double mortor_kd = 0;
void ControlInit()
{
mortorEN.setDirection("out");
mortorEN.setValue(1);
// 左电机方向: GPIO12 (右电机方向由 MotorController 自管, 但左In2也一样要设)
GPIO leftIn2(13);
leftIn2.setDirection("out");
leftIn2.setValue(1);
const int pwmchip[2] = {8, 8};
const int pwmnum[2] = {2, 1};
const int gpioNum[2] = {12, 13};
const int encoder_pwmchip[2] = {0, 3};
const int encoder_gpioNum[2] = {75, 72};
const int encoder_dir[2] = {1, -1};
const int pwmchip[2] = {8, 8};
const int pwmnum[2] = {2, 1};
const int gpioNum[2] = {12, 13};
const unsigned int period_ns = 50000;
for (int i = 0; i < 2; ++i)
{
motorController[i] = new MotorController(
pwmchip[i], pwmnum[i], gpioNum[i], period_ns,
mortor_kp, mortor_ki, mortor_kd, 0,
encoder_pwmchip[i], encoder_gpioNum[i], encoder_dir[i]
pwmchip[i], pwmnum[i], gpioNum[i], period_ns
);
}
}
void ControlUpdate(double speed, bool zebra_block)
{
if (zebra_block || !readFlag(start_file))
if (zebra_block || !g_cfg.start)
{
for (int i = 0; i < 2; ++i)
if (motorController[i]) motorController[i]->updateduty(0);
if (!readFlag(start_file)) mortorEN.setValue(0);
if (!g_cfg.start) mortorEN.setValue(0);
return;
}
// 弯道降速: 偏差越大,速度越低 (最低 60%)
double curve = 1.0 - std::abs(g_steer_deviation) * 0.4;
if (curve < 0.6) curve = 0.6;
double spd = speed * curve;
@@ -64,7 +61,7 @@ void ControlExit()
for (int i = 0; i < 2; ++i)
{
delete motorController[i];
std::cout << "motor" << i << " deleted\n";
motorController[i] = nullptr;
}
mortorEN.setValue(0);
}
-82
View File
@@ -1,82 +0,0 @@
/*
* @Author: ilikara 3435193369@qq.com
* @Date: 2024-10-11 06:19:57
* @LastEditors: ilikara 3435193369@qq.com
* @LastEditTime: 2024-12-01 03:54:06
* @FilePath: /smartcar/src/encoder.cpp
* @Description:
*
* Copyright (c) 2024 by ilikara 3435193369@qq.com, All Rights Reserved.
*/
#include "encoder.h"
ENCODER::ENCODER(int pwmNum, int gpioNum) : base_addr(PWM_BASE_ADDR + pwmNum * PWM_OFFSET), directionGPIO(gpioNum)
{
directionGPIO.setDirection("in");
control_buffer = map_register(base_addr + CONTROL_REG_OFFSET, PAGE_SIZE);
low_buffer = map_register(base_addr + LOW_BUFFER_OFFSET, PAGE_SIZE);
full_buffer = map_register(base_addr + FULL_BUFFER_OFFSET, PAGE_SIZE);
printf("Registers mapped successfully\n");
PWM_Init();
}
ENCODER::~ENCODER()
{
directionGPIO.~GPIO();
munmap(control_buffer, PAGE_SIZE);
munmap(low_buffer, PAGE_SIZE);
munmap(full_buffer, PAGE_SIZE);
}
void *ENCODER::map_register(uint32_t physical_address, size_t size)
{
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd == -1)
{
perror("Failed to open /dev/mem");
exit(EXIT_FAILURE);
}
void *mapped_addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, physical_address & ~(PAGE_SIZE - 1));
if (mapped_addr == MAP_FAILED)
{
perror("Failed to map memory");
close(mem_fd);
exit(EXIT_FAILURE);
}
close(mem_fd);
return (void *)((uintptr_t)mapped_addr + (physical_address & (PAGE_SIZE - 1)));
}
void ENCODER::PWM_Init(void)
{
uint32_t control_reg = 0;
control_reg |= CNTR_ENABLE_BIT;
control_reg |= MEASURE_PULSE_BIT;
control_reg |= INT_ENABLE_BIT;
REG_WRITE(control_buffer, control_reg);
printf("PWM initialized with control register: 0x%08X\n", control_reg);
}
void ENCODER::reset_counter(void)
{
uint32_t control_reg = REG_READ(control_buffer);
control_reg |= COUNTER_RESET_BIT;
REG_WRITE(control_buffer, control_reg);
}
double ENCODER::pulse_counter_update(void)
{
double value = 100000000.0 / REG_READ(full_buffer) / 1024.0 * (directionGPIO.readValue() * 2 - 1);
// reset_counter();
// printf("Encoder RPS: %8.1lf\n", 100000000.0 / REG_READ(full_buffer) / 1024.0 * (gpio_value * 2 - 1));
return value;
}
+17 -20
View File
@@ -1,37 +1,34 @@
#include "global.h"
#include <fstream>
CfgCache g_cfg;
double target_speed;
// 从文件读取双精度值
double readDoubleFromFile(const std::string &filename)
{
std::ifstream file(filename);
double value = 0.0;
if (file.is_open())
{
file >> value; // 读取文件中的值
file.close();
}
else
{
std::cerr << "Failed to open " << filename << std::endl;
}
if (file.is_open()) { file >> value; file.close(); }
return value;
}
// 从文件中读取标志
bool readFlag(const std::string &filename)
{
std::ifstream file(filename);
int flag = 0;
if (file.is_open())
{
file >> flag; // 读取文件中的更新标志
file.close();
}
else
{
std::cerr << "Failed to open " << filename << std::endl;
}
if (file.is_open()) { file >> flag; file.close(); }
return flag;
}
void cfg_load_all()
{
g_cfg.speed = readDoubleFromFile(speed_file);
g_cfg.foresee = readDoubleFromFile(foresee_file);
g_cfg.zebrasee = readDoubleFromFile(zebrasee_file);
g_cfg.deadband = readDoubleFromFile(deadband_file);
g_cfg.steer_gain = readDoubleFromFile(steer_gain_file);
g_cfg.center_bias = readDoubleFromFile(center_bias_file);
g_cfg.start = readFlag(start_file);
g_cfg.showImg = readFlag(showImg_file);
g_cfg.debug = readFlag(debug_file);
}
+11 -63
View File
@@ -15,11 +15,10 @@
// 全局图像变量
// ============================================================
cv::Mat raw_frame; // 摄像头原始帧 (320×240, BGR)
cv::Mat grayFrame; // 灰度图 (未在当前管线中使用,保留)
cv::Mat binarizedFrame; // HSV 双通道 Otsu 二值化结果
cv::Mat morphologyExFrame; // 形态学开运算后的图像
cv::Mat track; // 洪泛填充后的赛道区域蒙版
cv::Mat raw_frame;
cv::Mat binarizedFrame;
cv::Mat morphologyExFrame;
cv::Mat track;
// ============================================================
// 边界线数组 — 左/右边缘和中线的逐行列坐标
@@ -28,12 +27,9 @@ cv::Mat track; // 洪泛填充后的赛道区域蒙版
// 列坐标范围: 0 ~ line_tracking_width-1 (0~79)
// 未检测到边界 = -1
// ============================================================
std::vector<int> left_line; // 左边缘列号 (逐行)
std::vector<int> right_line; // 右边缘列号 (逐行)
std::vector<int> mid_line; // 中线列号 = (left+right)/2
std::vector<double> left_line_filtered; // 左边缘 EMA 滤波结果
std::vector<double> right_line_filtered; // 右边缘 EMA 滤波结果
std::vector<double> mid_line_filtered; // 中线滤波结果
std::vector<int> left_line;
std::vector<int> right_line;
std::vector<int> mid_line;
int line_tracking_width; // 处理宽度 = 80
int line_tracking_height; // 处理高度 = 60
@@ -118,7 +114,7 @@ cv::Mat find_road(cv::Mat &frame)
// 1. 形态学开运算去噪
// MORPH_CROSS: 十字形结构元素,2×2
// MORPH_OPEN: 先腐蚀(去除小白点) 再膨胀(恢复区域尺寸)
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(2, 2));
static cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(2, 2));
cv::morphologyEx(binarizedFrame, morphologyExFrame, cv::MORPH_OPEN, kernel);
// 2. 创建洪泛填充蒙版
@@ -150,7 +146,7 @@ cv::Mat find_road(cv::Mat &frame)
// 7. 从蒙版提取赛道区域
// mask 的外扩边框(±1) 用于 floodFill 的内部计算,实际区域在(1,1)起
// ROI 裁掉边框后即为赛道主体蒙版
cv::Mat outputImage = cv::Mat::zeros(line_tracking_width, line_tracking_height, CV_8UC1);
cv::Mat outputImage = cv::Mat::zeros(line_tracking_height, line_tracking_width, CV_8UC1);
mask(cv::Rect(1, 1, line_tracking_width, line_tracking_height)).copyTo(outputImage);
return outputImage;
@@ -191,16 +187,10 @@ void image_main()
left_line.clear();
right_line.clear();
mid_line.clear();
left_line_filtered.clear();
right_line_filtered.clear();
mid_line_filtered.clear();
left_line.resize(line_tracking_height, -1);
right_line.resize(line_tracking_height, -1);
mid_line.resize(line_tracking_height, -1);
left_line_filtered.resize(line_tracking_height, -1);
right_line_filtered.resize(line_tracking_height, -1);
mid_line_filtered.resize(line_tracking_height, -1);
// ── 5. 逐行最长连续段搜索 ──────────────────────────
//
@@ -270,28 +260,10 @@ void image_main()
}
}
// ── 6. 中线计算 + 丢线补全 → 自底向上 EMA 滤波 ─────
//
// 核心逻辑(从底部 row=59 往上到 row=10:
//
// 6a. 如果当前行左右边界均有效 → 中线 = (left + right) / 2
//
// 6b. 如果当前行丢线(左右均无效):
// 用下一行(row+1)的中线补全:
// → 中线下半区: 虚拟右边界在右边缘,左边界 = 下行中线
// → 中线上半区: 虚拟左边界在左边缘,右边界 = 下行中线
// 自动适应赛道偏左还是偏右的情况
//
// 6c. EMA 滤波 (指数移动平均):
// row 本身的值权重 = a (0.4), 下行滤波值权重 = 1-a (0.6)
// 从底部往顶部递推: 近处(底部)值稳定,远处(顶部)靠递推外推
// a=0.4 → 近处值占主导,但保留过去趋势的惯性
//
double a = 0.4; // EMA 系数: 平衡当前测量与历史递推
// ── 6. 中线计算 + 丢线补全 ─────
for (int row = line_tracking_height - 1; row >= 10; --row)
{
// ── 6b. 丢线补全 ──────────────────────────────
// ── 6a. 丢线补全 ──────────────────────────────
if (left_line[row] == -1 && right_line[row] == -1)
{
// 当前行完全丢线: 用下行(row+1)的中线来虚拟补线
@@ -317,29 +289,5 @@ void image_main()
// 正常行: 中线 = 左右边界中点
mid_line[row] = (left_line[row] + right_line[row]) / 2;
}
// ── 6c. EMA 滤波 (自底向上) ────────────────────
if (row == line_tracking_height - 1)
{
// 最底行(最近处): 无下行参考,直接使用原始值
left_line_filtered[row] = left_line[row];
right_line_filtered[row] = right_line[row];
mid_line_filtered[row] = mid_line[row];
}
else
{
// 公式: filtered[row] = a * raw[row] + (1-a) * filtered[row+1]
// a=0.4: 40% 当前行实测值 + 60% 下行滤波值的递推
// 效果: 近处值稳定,越往远处越靠递推,杜绝抖动的概率传播
left_line_filtered[row] = a * left_line[row]
+ (1 - a) * left_line_filtered[row + 1];
right_line_filtered[row] = a * right_line[row]
+ (1 - a) * right_line_filtered[row + 1];
// 中线的滤波值为左右滤波边界的均值(不是对原始中线做 EMA)
// 即: 先对左右边界各自滤波, 再求平均 → 减少中线突跳
// 原注释行: mid_line_filtered[row] = a*mid_line[row] + (1-a)*mid_line_filtered[row+1]
mid_line_filtered[row] = (left_line_filtered[row] + right_line_filtered[row]) / 2.0;
}
}
}
+44 -10
View File
@@ -495,17 +495,10 @@ static void init_lut() {
}
// ============================================================
// 前向推理
// 前向推理 — 模型计算体 (preproc 已由调用方完成)
// ============================================================
static void forward(const uint8* bgr) {
static void model_compute() {
float* in = m->preproc;
for (int c=0; c<3; ++c) {
int sc=2-c; float* ch=in+c*120*160;
for (int y=0; y<120; ++y) {
const uint8* row=bgr+y*160*3;
for (int x=0; x<160; ++x) ch[y*160+x]=g_lut[row[x*3+sc]];
}
}
// stem: 3→6, s2 (fused)
conv_bn_relu(m->stem, in, wf("s.0.weight"), nullptr,
@@ -528,6 +521,41 @@ static void forward(const uint8* bgr) {
conv2d(m->sz, m->sh, wf("sz.weight"), wf("sz.bias"), 15, 20, 24, 2, 1, 1, 1);
}
// ── 自写 640×480 → 3×120×160 float planar (4×4 box avg + BGR→RGB + /255) ──
static void preproc_640(const uint8* bgr) {
float* in = m->preproc;
const int sw=640, sh=480;
for(int c=0;c<3;++c){
int sc=2-c;
float* ch = in + c*120*160;
for(int y=0;y<120;++y){
int iy=y*4;
for(int x=0;x<160;++x){
int ix=x*4, sum=0;
for(int dy=0;dy<4;++dy)
for(int dx=0;dx<4;++dx)
sum += bgr[(iy+dy)*sw*3 + (ix+dx)*3 + sc];
ch[y*160+x] = (float)sum * (1.0f/(16.0f*255.0f));
}
}
}
}
// ── 原版: 160×120 BGR → 3×120×160 float planar ──
static void preproc_160(const uint8* bgr) {
float* in = m->preproc;
for (int c=0; c<3; ++c) {
int sc=2-c; float* ch=in+c*120*160;
for (int y=0; y<120; ++y) {
const uint8* row=bgr+y*160*3;
for (int x=0; x<160; ++x) ch[y*160+x]=g_lut[row[x*3+sc]];
}
}
}
// ── 内部: preproc + compute ──
static void forward(const uint8* bgr) { preproc_160(bgr); model_compute(); }
// ============================================================
// 接口
// ============================================================
@@ -555,7 +583,13 @@ bool model_v10_init(const char* path) {
}
int model_v10_detect(const uint8* bgr, int w, int h, DetectBoxV10* boxes, int max, const float* thresh) {
if(!g_rdy||w!=160||h!=120) return 0;
if(!g_rdy) return 0;
if(w==640 && h==480) {
preproc_640(bgr);
model_compute();
return decode(boxes,max,thresh);
}
if(w!=160||h!=120) return 0;
forward(bgr);
return decode(boxes,max,thresh);
}