Compare commits

..

34 Commits

Author SHA1 Message Date
16d6108292 修复4号命令不能重复执行的bug 2025-10-19 18:07:06 +08:00
30edbda7de hi3861软件 2025-10-18 16:05:39 +08:00
3d7a4dcb4d hi3861软件 2025-10-18 16:01:35 +08:00
6c7f013a0c 更新接口 2025-09-04 00:07:52 +08:00
95aa6b6bba LPR 2025-09-02 11:40:41 +08:00
739cd1d914 更新 README.md 2025-09-01 15:42:42 +08:00
01df759772 删除 yolopart/utils/__pycache__/video_capture.cpython-38.pyc 2025-09-01 15:37:13 +08:00
cb88e6fccd 删除 yolopart/utils/__pycache__/blue_plate_processor.cpython-38.pyc 2025-09-01 15:37:10 +08:00
80e995b47c 删除 yolopart/utils/__pycache__/__init__.cpython-38.pyc 2025-09-01 15:37:07 +08:00
f82df06a68 删除 yolopart/ui/__pycache__/video_widget.cpython-38.pyc 2025-09-01 15:36:59 +08:00
dc651af561 删除 yolopart/ui/__pycache__/main_window.cpython-38.pyc 2025-09-01 15:36:57 +08:00
9f9bd25ce7 删除 yolopart/ui/__pycache__/__init__.cpython-38.pyc 2025-09-01 15:36:54 +08:00
97ca0d75c2 删除 yolopart/models/__pycache__/yolo_detector.cpython-38.pyc 2025-09-01 15:36:45 +08:00
75cc3b8ea3 删除 yolopart/models/__pycache__/plate_recognizer.cpython-38.pyc 2025-09-01 15:36:37 +08:00
aca5703b9e 删除 yolopart/models/__pycache__/__init__.cpython-38.pyc 2025-09-01 15:36:32 +08:00
2eba46bc40 Merge pull request 'ocr-v1' (#4) from ocr-v1 into main
Reviewed-on: #4
2025-09-01 15:35:07 +08:00
f342d37d63 修改了模块的函数名,现在想用哪个模块直接导入即可 2025-09-01 15:33:05 +08:00
1c914cf89f OCR 2025-09-01 15:23:44 +08:00
afba7af80b OCR 2025-09-01 00:01:38 +08:00
8eef0d9414 Merge pull request 'yolorestart' (#1) from yolopart_restart into main
Reviewed-on: #1
2025-08-31 18:42:35 +08:00
8e8fda7fe9 Merge remote-tracking branch 'origin/ocr-v1' into ocr-v1
# Conflicts:
#	OCR_part/ocr_interface.py
2025-08-31 18:37:40 +08:00
9879cb1547 Merge pull request 'yolorestart' (#1) from yolopart_restart into main
Reviewed-on: #1
2025-08-31 18:36:36 +08:00
3829cf76ee Merge pull request 'yolorestart' (#1) from yolopart_restart into main
Reviewed-on: #1
2025-08-31 18:28:57 +08:00
c8a541ec11 Merge pull request 'yolorestart' (#1) from yolopart_restart into main
Reviewed-on: #1
2025-08-31 16:11:18 +08:00
b5839d2c36 更新 README.md 2025-08-31 12:53:11 +08:00
afe15b990a 更新 main.py 2025-08-31 12:19:25 +08:00
7f89965956 上传文件至 CRNN_part 2025-08-31 12:18:48 +08:00
c7ecc5325e 删除 CRNN_part/best_model.pth 2025-08-31 12:17:59 +08:00
01b286fce1 更新 CRNN_part/crnn_interface.py 2025-08-31 12:15:38 +08:00
85c8302fc1 Merge pull request 'yolopart_restart' (#3) from yolopart_restart into main
Reviewed-on: #3
2025-08-31 01:26:01 +08:00
0cd70df215 CRNN model 2025-08-31 01:16:08 +08:00
658560c34f Merge pull request 'yolorestart' (#2) from yolopart_restart into main
Reviewed-on: #2
2025-08-30 12:33:05 +08:00
c773a12f90 Merge remote-tracking branch 'origin/main' into yolopart_restart 2025-08-30 12:28:53 +08:00
a41a4a2236 yolorestart 2025-08-30 12:23:01 +08:00
39 changed files with 1904 additions and 982 deletions

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="pytorh" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="pytorh" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="pytorh" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/License_plate_recognition.iml" filepath="$PROJECT_DIR$/.idea/License_plate_recognition.iml" />
</modules>
</component>
</project>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

20
BUILD.gn Normal file
View File

@@ -0,0 +1,20 @@
static_library("barrier_gate_client") {
sources = [
# 根据功能划分文件
"demo_entry_cmsis.c", # 入口和主线程 源文件
"udp_client_test.c", # UDP客户端测试 源文件
"oled_ssd1306.c", # oled 显示屏驱动文件
"wifi_connecter.c", # wifi
"robot_sg90.c", # sg90 舵机
"json_parser.c", # JSON解析器
"display_helper.c" # 显示辅助文件
]
include_dirs = [
"//utils/native/lite/include",
"//kernel/liteos_m/kal/cmsis",
"//base/iot_hardware/peripheral/interfaces/kits",
"//foundation/communication/wifi_lite/interfaces/wifiservice", # HAL接口中的WiFi接口
]
}

View File

@@ -1,37 +0,0 @@
import numpy as np
def initialize_crnn_model():
"""
初始化CRNN模型
返回:
bool: 初始化是否成功
"""
# CRNN模型初始化代码
# 例如: 加载预训练模型、设置参数等
print("CRNN模型初始化完成占位")
return True
def crnn_predict(image_array):
"""
CRNN车牌号识别接口函数
参数:
image_array: numpy数组格式的车牌图像已经过矫正处理
返回:
list: 包含7个字符的列表代表车牌号的每个字符
例如: ['', 'A', '1', '2', '3', '4', '5']
"""
# 这是CRNN部分的占位函数
# 实际实现时,这里应该包含:
# 1. 图像预处理
# 2. CRNN模型推理
# 3. CTC解码
# 4. 后处理和字符识别
# 临时返回占位结果
placeholder_result = ['', '', '', '0', '0', '0', '0']
return placeholder_result

View File

@@ -1,36 +0,0 @@
import numpy as np
def initialize_ocr_model():
"""
初始化OCR模型
返回:
bool: 初始化是否成功
"""
# OCR模型初始化代码
# 例如: 加载预训练模型、设置参数等
print("OCR模型初始化完成占位")
return True
def ocr_predict(image_array):
"""
OCR车牌号识别接口函数
参数:
image_array: numpy数组格式的车牌图像已经过矫正处理
返回:
list: 包含7个字符的列表代表车牌号的每个字符
例如: ['', 'A', '1', '2', '3', '4', '5']
"""
# 这是OCR部分的占位函数
# 实际实现时,这里应该包含:
# 1. 图像预处理
# 2. OCR模型推理
# 3. 后处理和字符识别
# 临时返回占位结果
placeholder_result = ['', '', '', '0', '0', '0', '0']
return placeholder_result

155
README.md
View File

@@ -1,155 +0,0 @@
# 车牌识别系统
基于YOLO11 Pose模型的实时车牌检测与识别系统支持蓝牌和绿牌的检测、四角点定位、透视矫正和车牌号识别。
## 项目结构
```
License_plate_recognition/
├── main.py # 主程序入口PyQt界面
├── requirements.txt # 依赖包列表
├── README.md # 项目说明文档
├── yolopart/ # YOLO检测模块
│ ├── detector.py # YOLO检测器类
│ └── yolo11s-pose42.pt # YOLO pose模型文件
├── OCR_part/ # OCR识别模块
│ └── ocr_interface.py # OCR接口占位
└── CRNN_part/ # CRNN识别模块
└── crnn_interface.py # CRNN接口占位
```
## 功能特性
### 1. 实时车牌检测
- 基于YOLO11 Pose模型进行车牌检测
- 支持蓝牌类别0和绿牌类别1识别
- 实时摄像头画面处理
### 2. 四角点定位
- 检测车牌的四个角点right_bottom, left_bottom, left_top, right_top
- 只有检测到完整四个角点的车牌才进行后续处理
- 用黄色线条连接四个角点显示检测结果
### 3. 透视矫正
- 使用四个角点进行透视变换
- 将倾斜的车牌矫正为标准矩形
- 输出标准尺寸的车牌图像供识别使用
### 4. PyQt界面
- 左侧:实时摄像头画面显示
- 右侧:检测结果展示区域
- 顶部显示识别到的车牌数量
- 每行显示:车牌类型、矫正后图像、车牌号
- 美观的现代化界面设计
### 5. 模块化设计
- yolopart负责车牌定位和矫正
- OCR_part/CRNN_part负责车牌号识别接口已预留
- 各模块独立,便于维护和扩展
## 安装和使用
### 1. 环境要求
- Python 3.7+
- Windows/Linux/macOS
- 摄像头设备
### 2. 安装依赖
```bash
pip install -r requirements.txt
```
### 3. 模型文件
确保 `yolopart/yolo11s-pose42.pt` 模型文件存在。这是一个YOLO11 Pose模型专门训练用于车牌的四角点检测。
### 4. 运行程序
```bash
python main.py
```
### 5. 使用说明
1. 点击"启动摄像头"按钮开始检测
2. 将车牌对准摄像头
3. 系统会自动检测车牌并显示:
- 检测框和角点连线
- 右侧显示车牌类型、矫正图像和车牌号
4. 点击"停止摄像头"结束检测
## 模型输出格式
YOLO Pose模型输出包含
- **检测框**:车牌的边界框坐标
- **类别**0=蓝牌1=绿牌
- **置信度**:检测置信度分数
- **关键点**:四个角点坐标
- right_bottom右下角
- left_bottom左下角
- left_top左上角
- right_top右上角
## 接口说明
### OCR/CRNN接口
车牌号识别部分使用统一接口:
```python
# OCR接口
from OCR_part.ocr_interface import ocr_predict
result = ocr_predict(corrected_image) # 返回7个字符的列表
# CRNN接口
from CRNN_part.crnn_interface import crnn_predict
result = crnn_predict(corrected_image) # 返回7个字符的列表
```
### 输入参数
- `corrected_image`numpy数组格式的矫正后车牌图像
### 返回值
- 长度为7的字符列表包含车牌号的每个字符
- 例如:`['京', 'A', '1', '2', '3', '4', '5']`
## 开发说明
### 添加新的识别算法
1. 在对应目录OCR_part或CRNN_part实现识别函数
2. 确保函数签名与接口一致
3. 在main.py中切换调用的函数即可
### 自定义模型
1. 替换 `yolopart/yolo11s-pose42.pt` 文件
2. 确保新模型输出格式与现有接口兼容
3. 根据需要调整类别名称和数量
## 注意事项
1. **模型文件**确保YOLO模型文件路径正确
2. **摄像头权限**:程序需要摄像头访问权限
3. **光照条件**:良好的光照有助于提高检测精度
4. **车牌角度**:尽量保持车牌完整出现在画面中
5. **性能优化**:可根据硬件配置调整检测参数
## 故障排除
### 常见问题
1. **摄像头无法启动**:检查摄像头是否被其他程序占用
2. **模型加载失败**:确认模型文件路径和格式正确
3. **检测效果差**:调整光照条件或摄像头角度
4. **界面显示异常**检查PyQt5安装是否完整
### 调试模式
在代码中设置调试标志可以输出更多信息:
```python
# 在detector.py中设置verbose=True
results = self.model(image, conf=conf_threshold, verbose=True)
```
## 扩展功能
系统设计支持以下扩展:
- 多摄像头支持
- 批量图像处理
- 检测结果保存
- 网络API接口
- 数据库集成
- 性能统计和分析

56
chinese_char_map.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef CHINESE_CHAR_MAP_H
#define CHINESE_CHAR_MAP_H
#include <stdint.h>
// 中文字符映射结构体
typedef struct {
const char* utf8_char; // UTF-8编码的中文字符
uint8_t font_index; // 在fonts3数组中的索引
} ChineseCharMap;
// 中文字符映射表 - 根据fonts3数组中的字符顺序
static const ChineseCharMap chinese_char_map[] = {
{"", 0}, // ID:0 - 北京
{"", 1}, // ID:1 - 上海
{"", 2}, // ID:2 - 天津
{"", 3}, // ID:3 - 重庆
{"", 4}, // ID:4 - 河北
{"", 5}, // ID:5 - 山西
{"", 6}, // ID:6 - 内蒙古
{"", 7}, // ID:7 - 辽宁
{"", 8}, // ID:8 - 吉林
{"", 9}, // ID:9 - 黑龙江
{"", 10}, // ID:10 - 江苏
{"", 11}, // ID:11 - 浙江
{"", 12}, // ID:12 - 安徽
{"", 13}, // ID:13 - 福建
{"", 14}, // ID:14 - 江西
{"", 15}, // ID:15 - 山东
{"", 16}, // ID:16 - 河南
{"", 17}, // ID:17 - 湖北
{"", 18}, // ID:18 - 湖南
{"", 19}, // ID:19 - 广东
{"", 20}, // ID:20 - 广西
{"", 21}, // ID:21 - 海南
{"", 22}, // ID:22 - 四川
{"", 23}, // ID:23 - 贵州
{"", 24}, // ID:24 - 云南
{"", 25}, // ID:25 - 西藏
{"", 26}, // ID:26 - 陕西
{"", 27}, // ID:27 - 甘肃
{"", 28}, // ID:28 - 青海
{"", 29}, // ID:29 - 宁夏
{"", 30}, // ID:30 - 新疆
{"", 31}, // ID:31 - 禁止
{"", 32}, // ID:32 - 通行
{"", 33} // ID:33 - 行驶
};
// 映射表大小
#define CHINESE_CHAR_MAP_SIZE (sizeof(chinese_char_map) / sizeof(ChineseCharMap))
// 函数声明
int FindChineseCharIndex(const char* utf8_char);
#endif // CHINESE_CHAR_MAP_H

525
demo_entry_cmsis.c Normal file
View File

@@ -0,0 +1,525 @@
#include <stdio.h> // 标准输入输出
#include <unistd.h> // POSIX标准接口
#include <string.h> // 字符串处理(操作字符数组)
#include "ohos_init.h" // 用于初始化服务(services)和功能(features)
#include "cmsis_os2.h" // CMSIS-RTOS API V2
#include "wifi_connecter.h" // easy wifi (station模式)
#include "oled_ssd1306.h" // OLED驱动接口
#include "json_parser.h"
#include "display_helper.h" // 显示辅助函数
#include "robot_sg90.h" // 舵机控制接口
#include "iot_gpio.h"
#include "hi_io.h"
#include "hi_time.h"
#include "iot_gpio.h"
#include "hi_adc.h"
#include "iot_errno.h"
#if 1
// 定义一个宏用于标识SSID。请根据实际情况修改
#define PARAM_HOTSPOT_SSID "tarikPura"
// 定义一个宏,用于标识密码。请根据实际情况修改
#define PARAM_HOTSPOT_PSK "66668888"
#elif
#define PARAM_HOTSPOT_SSID "DYJY"
// 定义一个宏,用于标识密码。请根据实际情况修改
#define PARAM_HOTSPOT_PSK "12345678"
#endif
// 定义一个宏,用于标识加密方式
#define PARAM_HOTSPOT_TYPE WIFI_SEC_TYPE_PSK
// 定义一个宏用于标识UDP服务器IP地址。请根据实际情况修改
#define PARAM_SERVER_ADDR "192.168.43.137"
#define GPIO5 5
#define ADC_TEST_LENGTH (20)
#define VLT_MIN (100)
#define KEY_INTERRUPT_PROTECT_TIME (30)
unsigned short g_adc_buf[ADC_TEST_LENGTH] = { 0 };
unsigned short g_gpio5_adc_buf[ADC_TEST_LENGTH] = { 0 };
unsigned int g_gpio5_tick = 0;
int control_flag = 0;
extern char response[128];
extern JsonCommand g_current_command; // 外部声明JSON命令变量
// 舵机控制函数声明
extern void servo_rotate_clockwise_90(void);
extern void servo_rotate_counter_clockwise_90(void);
extern void regress_middle(void);
uint8_t fonts3[] = {
/*-- ID:0,字符:"京",ASCII编码:BEA9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x08,0x08,0x08,0xe8,0x28,0x28,0x29,0x2e,0x28,0x28,0x28,0xf8,0x28,0x0c,0x08,0x00,
0x00,0x00,0x40,0x23,0x1a,0x42,0x82,0x7e,0x02,0x0a,0x12,0x33,0x60,0x00,0x00,0x00,
/*-- ID:1,字符:"沪",ASCII编码:BBA6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x22,0x64,0x0c,0x80,0x00,0xf8,0x88,0x89,0x8a,0x8e,0x88,0x88,0xfc,0x08,0x00,
0x04,0x04,0xfc,0x03,0x80,0x60,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,
/*-- ID:2,字符:"津",ASCII编码:BDF2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x20,0x42,0xc4,0x0c,0x10,0x54,0x54,0x54,0xff,0x54,0x54,0x54,0x7e,0x14,0x10,0x00,
0x04,0x04,0xfc,0x02,0x11,0x12,0x12,0x12,0xff,0x12,0x12,0x13,0x1a,0x10,0x00,0x00,
/*-- ID:3,字符:"渝",ASCII编码:D3E5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x22,0x64,0x0c,0xa0,0xd0,0x48,0x54,0xd2,0x13,0x94,0x08,0xd0,0x30,0x10,0x00,
0x04,0x04,0xfe,0x01,0x00,0xff,0x12,0x92,0xff,0x00,0x5f,0x80,0x7f,0x00,0x00,0x00,
/*-- ID:4,字符:"冀",ASCII编码:BCBD,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x08,0x0a,0xea,0xaa,0xaa,0xaa,0xaf,0xe0,0xaf,0xaa,0xaa,0xaa,0xfa,0x28,0x0c,0x00,
0x20,0xa0,0xab,0x6a,0x2a,0x3e,0x2a,0x2b,0x2a,0x3e,0x2a,0x6a,0xab,0xa0,0x20,0x00,
/*-- ID:5,字符:"晋",ASCII编码:BDFA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x42,0x4a,0x52,0x42,0x7e,0x42,0x42,0x42,0x7e,0x42,0xd2,0x4b,0x62,0x40,0x00,
0x00,0x00,0x00,0xff,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0xff,0x01,0x00,0x00,0x00,
/*-- ID:6,字符:"蒙",ASCII编码:C3C9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x20,0x9a,0x8a,0x8a,0xaa,0xaf,0xaa,0xaa,0xaa,0xaf,0xaa,0x8a,0x8b,0xaa,0x18,0x00,
0x00,0x50,0x52,0x2a,0x2a,0x15,0x4b,0x86,0x7c,0x04,0x0a,0x13,0x20,0x60,0x20,0x00,
/*-- ID:7,字符:"辽",ASCII编码:C1C9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0x21,0x22,0xe6,0x00,0x02,0x02,0x02,0x02,0xf2,0x12,0x0a,0x06,0x02,0x00,0x00,
0x00,0x40,0x20,0x1f,0x20,0x40,0x40,0x48,0x50,0x4f,0x40,0x40,0x40,0x60,0x20,0x00,
/*-- ID:8,字符:"吉",ASCII编码:BCAA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x08,0x08,0x88,0x88,0x88,0x88,0x88,0xff,0x88,0x88,0x88,0xc8,0x88,0x0c,0x08,0x00,
0x00,0x00,0x00,0xfc,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xfe,0x04,0x00,0x00,0x00,
/*-- ID:9,字符:"黑",ASCII编码:BADA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0x00,0xfe,0x8a,0x92,0xb2,0x82,0xfe,0x82,0xa2,0x9a,0x92,0xff,0x02,0x00,0x00,
0x08,0x8a,0x6a,0x0a,0x2a,0xca,0x0a,0x0f,0x2a,0xca,0x0a,0x2a,0x4a,0xca,0x08,0x00,
/*-- ID:10,字符:"苏",ASCII编码:CBD5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x04,0x04,0x44,0x44,0x5f,0x44,0xf4,0x44,0x44,0x44,0x5f,0xe4,0x44,0x06,0x04,0x00,
0x00,0x88,0x46,0x20,0x10,0x0c,0x03,0x00,0x00,0x40,0x80,0x7f,0x02,0x04,0x0c,0x00,
/*-- ID:11,字符:"浙",ASCII编码:D5E3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x22,0x64,0x0c,0x90,0x10,0xff,0x10,0x90,0xfc,0x44,0x44,0xc2,0x62,0x40,0x00,
0x04,0x04,0xfe,0x01,0x42,0x82,0x7f,0x41,0x20,0x1f,0x00,0x00,0xff,0x00,0x00,0x00,
/*-- ID:12,字符:"皖",ASCII编码:CDEE,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0xf8,0x8c,0x8b,0x88,0xf8,0x10,0x0c,0x24,0x25,0x26,0x34,0x24,0x94,0x0c,0x00,
0x00,0x3f,0x10,0x10,0x10,0xbf,0x41,0x31,0x0f,0x01,0x01,0x3f,0x41,0x41,0x71,0x00,
/*-- ID:13,字符:"闽",ASCII编码:C3F6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0xfc,0x01,0x02,0xe6,0x20,0x22,0xfa,0x22,0x22,0xf2,0x22,0x02,0xff,0x02,0x00,
0x00,0xff,0x00,0x20,0x27,0x22,0x22,0x3f,0x12,0x12,0x1b,0x70,0x80,0x7f,0x00,0x00,
/*-- ID:14,字符:"赣",ASCII编码:B8D3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x12,0xd6,0x5a,0x53,0x5a,0xd6,0x12,0x28,0x64,0x57,0xca,0x56,0x52,0x20,0x20,0x00,
0x10,0x17,0x15,0xfd,0x15,0x17,0x10,0x81,0xbd,0x45,0x35,0x45,0x7d,0x81,0x00,0x00,
/*-- ID:15,字符:"鲁",ASCII编码:C2B3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x10,0xf8,0xac,0xaa,0xab,0xaa,0xfa,0xae,0xaa,0xaa,0xa8,0xfc,0x08,0x00,0x00,
0x02,0x02,0x02,0xfa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xfe,0x0a,0x02,0x02,0x00,
/*-- ID:16,字符:"豫",ASCII编码:D4A5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x42,0x52,0xe2,0x5a,0xc6,0x50,0xf8,0x94,0xd3,0xba,0x96,0x92,0xf8,0x10,0x00,
0x00,0x40,0x80,0x7f,0x00,0x10,0x54,0x4a,0x25,0x92,0xfc,0x0c,0x12,0x61,0x20,0x00,
/*-- ID:17,字符:"鄂",ASCII编码:B6F5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0x5e,0x52,0x5e,0x40,0x5e,0x52,0x5e,0x00,0xfe,0x02,0x42,0xb2,0x0e,0x00,0x00,
0x01,0x01,0x0d,0x4b,0x89,0x89,0x7d,0x09,0x01,0xff,0x08,0x10,0x20,0x11,0x0e,0x00,
/*-- ID:18,字符:"湘",ASCII编码:CFE6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x22,0x64,0x0c,0x10,0xd0,0xff,0x90,0x10,0xfc,0x44,0x44,0x44,0xfe,0x04,0x00,
0x04,0x04,0xfe,0x05,0x03,0x00,0xff,0x00,0x01,0xff,0x44,0x44,0x44,0xff,0x00,0x00,
/*-- ID:19,字符:"粤",ASCII编码:D4C1,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0x00,0x00,0xfe,0x92,0xd6,0x93,0xfe,0x92,0xd6,0x92,0xff,0x02,0x00,0x00,0x00,
0x02,0x02,0x02,0x02,0x0a,0x0e,0x0a,0x0a,0x4a,0x8a,0x4a,0x3a,0x02,0x03,0x02,0x00,
/*-- ID:20,字符:"桂",ASCII编码:B9F0,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x10,0xd0,0xff,0x90,0x50,0x48,0x48,0x48,0xff,0x48,0x48,0x4c,0x68,0x40,0x00,
0x04,0x03,0x00,0xff,0x40,0x41,0x44,0x44,0x44,0x7f,0x44,0x44,0x46,0x64,0x40,0x00,
/*-- ID:21,字符:"琼",ASCII编码:C7ED,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x44,0x44,0xfc,0x46,0x44,0x08,0xe8,0x28,0x29,0x2a,0x28,0x28,0xf8,0x2c,0x08,0x00,
0x10,0x30,0x1f,0x08,0x08,0x20,0x13,0x5a,0x82,0x7e,0x02,0x0a,0x13,0x30,0x00,0x00,
/*-- ID:22,字符:"川",ASCII编码:B4A8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0x00,0x00,0xfe,0x00,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x00,0xff,0x00,0x00,
0x00,0x40,0x20,0x1f,0x00,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x00,0xff,0x00,0x00,
/*-- ID:23,字符:"贵",ASCII编码:B9F3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x40,0x5c,0x54,0x54,0x54,0x54,0x7f,0x54,0x54,0x54,0xd4,0x5e,0x44,0x40,0x00,
0x00,0x00,0x80,0x9f,0x41,0x41,0x21,0x1d,0x21,0x21,0x41,0x5f,0x81,0x00,0x00,0x00,
/*-- ID:24,字符:"云",ASCII编码:D4C6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x40,0x40,0x44,0x44,0x44,0xc4,0x44,0x44,0x44,0x46,0x44,0x40,0x60,0x40,0x00,
0x00,0x00,0x40,0x60,0x58,0x46,0x41,0x40,0x40,0x40,0x50,0x60,0xc0,0x00,0x00,0x00,
/*-- ID:25,字符:"藏",ASCII编码:B2D8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x02,0xf2,0x82,0xf2,0x12,0xdf,0x52,0xd2,0x5f,0x12,0xfe,0x12,0x16,0x9b,0x12,0x00,
0x90,0x4e,0x22,0x1f,0x00,0x7f,0x25,0x3d,0xa7,0x40,0x2f,0x30,0x4c,0x83,0xe0,0x00,
/*-- ID:26,字符:"陕",ASCII编码:C9C2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0xfe,0x22,0x5a,0x86,0x08,0x28,0x48,0x08,0xff,0x08,0x48,0x2c,0x88,0x00,0x00,
0x00,0xff,0x04,0x08,0x87,0x41,0x21,0x11,0x0d,0x03,0x0d,0x11,0x61,0xc1,0x41,0x00,
/*-- ID:27,字符:"甘",ASCII编码:B8CA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x10,0x10,0x10,0x10,0xff,0x10,0x10,0x10,0x10,0x10,0xff,0x10,0x10,0x18,0x10,0x00,
0x00,0x00,0x00,0x00,0xff,0x42,0x42,0x42,0x42,0x42,0xff,0x00,0x00,0x00,0x00,0x00,
/*-- ID:28,字符:"青",ASCII编码:C7E0,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x44,0x54,0x54,0x54,0x54,0x54,0x7f,0x54,0x54,0x54,0xd4,0x56,0x44,0x40,0x00,
0x00,0x00,0x00,0xff,0x15,0x15,0x15,0x15,0x15,0x55,0x95,0x7f,0x01,0x00,0x00,0x00,
/*-- ID:29,字符:"宁",ASCII编码:C4FE,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x50,0x4c,0x44,0x44,0x44,0x44,0x45,0xc6,0x44,0x44,0x44,0x44,0x44,0x54,0x4c,0x00,
0x00,0x00,0x00,0x00,0x00,0x40,0x80,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*-- ID:30,字符:"新",ASCII编码:D0C2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x44,0x54,0x65,0xc6,0x64,0xd6,0x44,0x40,0xfc,0x44,0x42,0xc3,0x62,0x40,0x00,
0x20,0x11,0x49,0x81,0x7f,0x01,0x05,0x29,0x18,0x07,0x00,0x00,0xff,0x00,0x00,0x00,
/*-- ID:31,字符:"禁",ASCII编码:BDFB,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x24,0x14,0x0c,0x7f,0x14,0x24,0x20,0x14,0x0c,0x7f,0x0c,0x16,0x24,0x40,0x00,
0x04,0x04,0x45,0x25,0x15,0x45,0x85,0x7d,0x05,0x05,0x15,0x25,0x65,0x04,0x04,0x00,
/*-- ID:32,字符:"通",ASCII编码:CDA8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x40,0x42,0x44,0xcc,0x00,0x00,0xf1,0x91,0x95,0xf9,0x95,0x93,0xf9,0x10,0x00,0x00,
0x00,0x40,0x20,0x1f,0x20,0x40,0xbf,0x84,0x84,0xbf,0x94,0xa4,0x9f,0xc0,0x40,0x00,
/*-- ID:33,字符:"行",ASCII编码:D0D0,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x00,0x10,0x88,0xc4,0x23,0x40,0x42,0x42,0x42,0x42,0x42,0xc2,0x43,0x62,0x40,0x00,
0x02,0x01,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x40,0x80,0x7f,0x00,0x00,0x00,0x00};
// 定义一个宏用于标识UDP服务器端口
#define PARAM_SERVER_PORT 8081
void switch_init(void)
{
IoTGpioInit(5);
hi_io_set_func(5, 0);
IoTGpioSetDir(5, IOT_GPIO_DIR_IN);
hi_io_set_pull(5, 1);
}
//按键中断响应函数
void gpio5_isr_func_mode(void)
{
printf("gpio5_isr_func_mode start\n");
unsigned int tick_interval = 0;
unsigned int current_gpio5_tick = 0;
current_gpio5_tick = hi_get_tick();
tick_interval = current_gpio5_tick - g_gpio5_tick;
if (tick_interval < KEY_INTERRUPT_PROTECT_TIME) {
return NULL;
}
g_gpio5_tick = current_gpio5_tick;
control_flag = !control_flag;
}
unsigned char get_gpio5_voltage(void *param)
{
int i;
unsigned short data;
unsigned int ret;
unsigned short vlt;
float voltage;
float vlt_max = 0;
float vlt_min = VLT_MIN;
hi_unref_param(param);
memset_s(g_gpio5_adc_buf, sizeof(g_gpio5_adc_buf), 0x0, sizeof(g_gpio5_adc_buf));
for (i = 0; i < ADC_TEST_LENGTH; i++) {
ret = hi_adc_read(HI_ADC_CHANNEL_2, &data, HI_ADC_EQU_MODEL_4, HI_ADC_CUR_BAIS_DEFAULT, 0xF0);
//ADC_Channal_2 自动识别模式 CNcomment:4次平均算法模式 CNend */
if (ret != IOT_SUCCESS) {
printf("ADC Read Fail\n");
return NULL;
}
g_gpio5_adc_buf[i] = data;
}
for (i = 0; i < ADC_TEST_LENGTH; i++) {
vlt = g_gpio5_adc_buf[i];
voltage = (float)vlt * 1.8 * 4 / 4096.0;
/* vlt * 1.8* 4 / 4096.0为将码字转换为电压 */
vlt_max = (voltage > vlt_max) ? voltage : vlt_max;
vlt_min = (voltage < vlt_min) ? voltage : vlt_min;
}
printf("vlt_max is %f\r\n", vlt_max);
if (vlt_max > 0.6 && vlt_max < 1.0) {
gpio5_isr_func_mode();
}
}
//按键中断
void interrupt_monitor(void)
{
unsigned int ret = 0;
/*gpio5 switch2 mode*/
g_gpio5_tick = hi_get_tick();
ret = IoTGpioRegisterIsrFunc(GPIO5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, get_gpio5_voltage, NULL);
if (ret == IOT_SUCCESS) {
printf(" register gpio5\r\n");
}
}
void OledShowChinese3(uint8_t x, uint8_t y, uint8_t idx)
{
// 控制循环
uint8_t t;
// 显示汉字的上半部分
OledSetPosition(x, y);
for (t = 0; t < 16; t++)
{
WriteData(fonts3[32 * idx + t]);
}
// 显示汉字的下半部分
OledSetPosition(x, y + 1);
for (t = 16; t < 32; t++)
{
WriteData(fonts3[32 * idx + t]);
}
}
static void controlTask(void *arg)
{
(void)arg;
int control_temp = control_flag;
int count = 0;
static int display_timer = 0; // 显示计时器
while (1)
{
if(control_flag != control_temp)
{
count = 0;
display_timer = 0;
OledClearScreen(); // 使用新的清屏函数
control_temp = control_flag;
// 处理新的JSON命令格式
if(control_flag == CMD_ROTATE_DISPLAY_CLEAR) // 命令1顺时针90°+显示字符串+10秒后逆时针90°+清屏
{
printf("Command 1: Rotate clockwise, display text, wait 10s, rotate back, clear\r\n");
// 顺时针旋转90度
servo_rotate_clockwise_90();
// 显示字符串
if (strlen(g_current_command.text) > 0) {
printf("Displaying text: %s\r\n", g_current_command.text);
DisplayMixedString(0, 0, g_current_command.text);
}
display_timer = 10; // 设置10秒计时器
}
else if(control_flag == CMD_ROTATE_CLOCKWISE) // 命令2顺时针90°
{
printf("Command 2: Rotate clockwise 90 degrees\r\n");
servo_rotate_clockwise_90();
}
else if(control_flag == CMD_ROTATE_COUNTER) // 命令3逆时针90°
{
printf("Command 3: Rotate counter-clockwise 90 degrees\r\n");
servo_rotate_counter_clockwise_90();
}
else if(control_flag == CMD_DISPLAY_ONLY) // 命令4只显示字符串舵机不动
{
printf("Command 4: Display text only, no servo movement\r\n");
// 只显示字符串,不控制舵机
if (strlen(g_current_command.text) > 0) {
printf("Displaying text: %s\r\n", g_current_command.text);
DisplayMixedString(0, 0, g_current_command.text);
}
display_timer = 2; // 设置2秒计时器
}
// 兼容旧命令的处理逻辑只有在非JSON命令时才执行
else if(control_flag == 0 && g_current_command.cmd == 0) // 兼容旧命令:关闭
{
regress_middle();
printf("barrier off\n");
}
else if(control_flag == 1 && g_current_command.cmd == 0) // 兼容旧命令开启只有非JSON命令时
{
servo_rotate_counter_clockwise_90();
printf("barrier on\n");
}
// 兼容旧的复杂字符串解析逻辑 - 只有在非JSON命令时才执行
if(control_flag == 2 && g_current_command.cmd == 0)
{
char prefix[20]; // 存储前部分字符串
int num; // 存储后部分数字
int index1 = 0;
// 使用 sscanf 解析字符串
int result = sscanf(response, "%[^i]index:%dflag:%d", prefix, &num,&index1);
if (result == 3) {
memset(response,0,sizeof(response));
OledShowChinese3(0,0,num);
OledShowString(18, 0, prefix, FONT8x16);
OledShowChinese3(0 , 2, index1);
OledShowChinese3(18 , 2, 33);
}
if (index1 == 32)
{
servo_rotate_counter_clockwise_90();
}
}
}
// 处理命令1的10秒计时器
if(control_flag == CMD_ROTATE_DISPLAY_CLEAR && display_timer > 0)
{
display_timer--;
if(display_timer == 0)
{
printf("10 seconds elapsed, rotating back and clearing screen\r\n");
// 逆时针旋转90度
servo_rotate_counter_clockwise_90();
// 清屏
OledClearScreen();
// 重置控制标志
control_flag = 0;
}
}
// 处理命令4的2秒计时器
if(control_flag == CMD_DISPLAY_ONLY && display_timer > 0)
{
display_timer--;
if(display_timer == 0)
{
printf("2 seconds elapsed, clearing screen for command 4\r\n");
// 清屏
OledClearScreen();
// 重置控制标志使其能够响应下一个命令4
control_flag = 0;
}
}
// 兼容旧的计时逻辑
if(control_flag == 2 && g_current_command.cmd == 0)
{
count++;
if(count > 10)
{
control_flag = 0;
count = 0;
}
}
sleep(1);
}
}
// 主线程函数
static void NetDemoTask(void *arg)
{
(void)arg;
int control_temp = 0;
// 定义热点配置
WifiDeviceConfig config = {0};
// 设置热点配置中的SSID
strcpy(config.ssid, PARAM_HOTSPOT_SSID);
// 设置热点配置中的密码
strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
// 设置热点配置中的加密方式(Wi-Fi security types)
config.securityType = PARAM_HOTSPOT_TYPE;
// 显示启动信息
printf("=== Hi3861 智能闸机控制系统启动 ===\r\n");
printf("正在连接WiFi: %s\r\n", PARAM_HOTSPOT_SSID);
printf("服务器地址: %s:%d\r\n", PARAM_SERVER_ADDR, PARAM_SERVER_PORT);
// 在OLED上显示启动信息
OledFillScreen(0x00);
OledShowString(0, 0, "Starting...", FONT8x16);
OledShowString(0, 2, PARAM_HOTSPOT_SSID, FONT8x16);
// 等待100ms
osDelay(10);
// 连接到热点
printf("开始连接WiFi...\r\n");
int netId = ConnectToHotspot(&config);
// 检查是否成功连接到热点
if (netId < 0)
{
// 连接到热点失败
printf("WiFi连接失败错误代码: %d\r\n", netId);
OledFillScreen(0x00);
OledShowString(0, 0, "WiFi Failed!", FONT8x16);
OledShowString(0, 2, "Check Config", FONT8x16);
return;
}
// 连接到热点成功,显示连接成功信息
printf("WiFi连接成功网络ID: %d\r\n", netId);
OledFillScreen(0x00);
OledShowString(0, 0, "WiFi Connected", FONT8x16);
OledShowString(0, 2, "Starting UDP...", FONT8x16);
// 等待一段时间确保连接稳定
printf("等待网络稳定...\r\n");
sleep(3);
// 运行UDP客户端测试发送IP地址到服务器
printf("启动UDP客户端连接服务器 %s:%d\r\n", PARAM_SERVER_ADDR, PARAM_SERVER_PORT);
UdpClientTest(PARAM_SERVER_ADDR, PARAM_SERVER_PORT);
// 断开热点连接
printf("断开WiFi连接...\r\n");
DisconnectWithHotspot(netId);
printf("WiFi连接已断开\r\n");
}
// 入口函数
static void NetDemoEntry(void)
{
switch_init();
interrupt_monitor();
// 初始化OLED
OledInit();
// 全屏填充黑色
OledFillScreen(0x00);
// OLED显示APP标题
OledShowString(0, 0, "UdpClient Test", FONT8x16);
// 定义线程属性
osThreadAttr_t attr;
attr.name = "NetDemoTask";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 10240;
attr.priority = osPriorityNormal;
// 创建线程
if (osThreadNew(NetDemoTask, NULL, &attr) == NULL)
{
printf("[NetDemoEntry] Falied to create NetDemoTask!\n");
}
attr.name = "controlTask";
attr.stack_size = 2048;
if (osThreadNew(controlTask, NULL, &attr) == NULL)
{
printf("[control] Falied to create NetDemoTask!\n");
}
}
// 运行入口函数
SYS_RUN(NetDemoEntry);

64
display_helper.c Normal file
View File

@@ -0,0 +1,64 @@
#include "display_helper.h"
#include "chinese_char_map.h"
#include "json_parser.h"
#include "oled_ssd1306.h"
#include <string.h>
#include <stdio.h>
// 显示混合字符串(中文+英文数字)
void DisplayMixedString(uint8_t start_x, uint8_t start_y, const char* text) {
if (!text) {
return;
}
uint8_t x = start_x;
uint8_t y = start_y;
int i = 0;
int text_len = strlen(text);
printf("DisplayMixedString: Processing text '%s' (length: %d)\r\n", text, text_len);
while (i < text_len) {
// 检查是否为UTF-8中文字符通常以0xE开头的3字节序列
if ((unsigned char)text[i] >= 0xE0 && i + 2 < text_len) {
// 提取3字节的UTF-8中文字符
char chinese_char[4] = {0};
chinese_char[0] = text[i];
chinese_char[1] = text[i + 1];
chinese_char[2] = text[i + 2];
chinese_char[3] = '\0';
printf("Found Chinese char: %02X %02X %02X\r\n",
(unsigned char)chinese_char[0],
(unsigned char)chinese_char[1],
(unsigned char)chinese_char[2]);
// 查找字符在fonts3数组中的索引
int font_index = FindChineseCharIndex(chinese_char);
if (font_index >= 0) {
printf("Displaying Chinese char at index %d, position (%d, %d)\r\n", font_index, x, y);
OledShowChinese3(x, y, font_index);
x += 16; // 中文字符宽度为16像素
} else {
printf("Chinese char not found in font table, displaying as '?'\r\n");
// 如果找不到字符,显示问号
OledShowString(x, y, "?", FONT8x16);
x += 8; // 英文字符宽度为8像素
}
i += 3; // 跳过3字节的UTF-8字符
} else {
// 处理ASCII字符英文、数字、符号
char ascii_char[2] = {text[i], '\0'};
printf("Found ASCII char: '%c' (0x%02X)\r\n", text[i], (unsigned char)text[i]);
OledShowString(x, y, ascii_char, FONT8x16);
x += 8; // 英文字符宽度为8像素
i++;
}
// 检查是否需要换行假设屏幕宽度为128像素
if (x >= 120) {
x = start_x;
y += 2; // 每行高度为2个单位16像素
}
}
}

9
display_helper.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef DISPLAY_HELPER_H
#define DISPLAY_HELPER_H
#include <stdint.h>
// 显示混合字符串(中文+英文数字)
void DisplayMixedString(uint8_t start_x, uint8_t start_y, const char* text);
#endif // DISPLAY_HELPER_H

72
json_parser.c Normal file
View File

@@ -0,0 +1,72 @@
#include "json_parser.h"
#include "chinese_char_map.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 简单的JSON解析函数解析格式: {"cmd":1,"text":"hello"}
int ParseJsonCommand(const char* json_str, JsonCommand* command) {
if (!json_str || !command) {
return -1;
}
// 初始化结构体
command->cmd = 0;
memset(command->text, 0, MAX_TEXT_LENGTH);
// 查找cmd字段
const char* cmd_pos = strstr(json_str, "\"cmd\":");
if (cmd_pos) {
cmd_pos += 6; // 跳过"cmd":
// 跳过空格
while (*cmd_pos == ' ') cmd_pos++;
command->cmd = atoi(cmd_pos);
}
// 查找text字段
const char* text_pos = strstr(json_str, "\"text\":");
if (text_pos) {
text_pos += 7; // 跳过"text":
// 跳过空格和引号
while (*text_pos == ' ' || *text_pos == '\"') text_pos++;
// 复制文本直到遇到引号或字符串结束
int i = 0;
while (*text_pos && *text_pos != '\"' && i < MAX_TEXT_LENGTH - 1) {
command->text[i++] = *text_pos++;
}
command->text[i] = '\0';
}
return 0;
}
// 创建IP地址消息格式: {"type":"ip","address":"192.168.1.100"}
int CreateIpMessage(char* buffer, int buffer_size, const char* ip_address) {
if (!buffer || !ip_address || buffer_size < 50) {
return -1;
}
int len = snprintf(buffer, buffer_size,
"{\"type\":\"ip\",\"address\": \"%s\"}",
ip_address);
return (len > 0 && len < buffer_size) ? 0 : -1;
}
// 查找中文字符在fonts3数组中的索引
int FindChineseCharIndex(const char* utf8_char) {
if (!utf8_char) {
return -1;
}
// 遍历映射表查找匹配的字符
for (int i = 0; i < CHINESE_CHAR_MAP_SIZE; i++) {
if (strcmp(utf8_char, chinese_char_map[i].utf8_char) == 0) {
return chinese_char_map[i].font_index;
}
}
// 未找到匹配的字符
return -1;
}

29
json_parser.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef JSON_PARSER_H
#define JSON_PARSER_H
#include <stdint.h>
// 定义命令类型
#define CMD_ROTATE_DISPLAY_CLEAR 1 // 顺时针90°+显示字符串+10秒后逆时针90°+清屏
#define CMD_ROTATE_CLOCKWISE 2 // 顺时针90°
#define CMD_ROTATE_COUNTER 3 // 逆时针90°
#define CMD_DISPLAY_ONLY 4 // 只显示字符串,舵机不动
// 定义最大字符串长度
#define MAX_TEXT_LENGTH 64
// JSON命令结构体
typedef struct {
int cmd; // 命令类型
char text[MAX_TEXT_LENGTH]; // 显示文本
} JsonCommand;
// 函数声明
int ParseJsonCommand(const char* json_str, JsonCommand* command);
// 创建IP地址消息格式: {"type":"ip","address":"192.168.1.100"}
int CreateIpMessage(char* buffer, int buffer_size, const char* ip_address);
// 查找中文字符在fonts3数组中的索引
int FindChineseCharIndex(const char* utf8_char);
#endif // JSON_PARSER_H

411
main.py
View File

@@ -1,411 +0,0 @@
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QScrollArea, QFrame, QSizePolicy
)
from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread
from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter, QPen, QColor
import os
from yolopart.detector import LicensePlateYOLO
from OCR_part.ocr_interface import ocr_predict
#from CRNN_part.crnn_interface import crnn_predict不使用CRNN
class CameraThread(QThread):
"""摄像头线程类"""
frame_ready = pyqtSignal(np.ndarray)
def __init__(self):
super().__init__()
self.camera = None
self.running = False
def start_camera(self):
"""启动摄像头"""
self.camera = cv2.VideoCapture(0)
if self.camera.isOpened():
self.running = True
self.start()
return True
return False
def stop_camera(self):
"""停止摄像头"""
self.running = False
if self.camera:
self.camera.release()
self.quit()
self.wait()
def run(self):
"""线程运行函数"""
while self.running:
if self.camera and self.camera.isOpened():
ret, frame = self.camera.read()
if ret:
self.frame_ready.emit(frame)
self.msleep(30) # 约30fps
class LicensePlateWidget(QWidget):
"""单个车牌结果显示组件"""
def __init__(self, plate_id, class_name, corrected_image, plate_number):
super().__init__()
self.plate_id = plate_id
self.init_ui(class_name, corrected_image, plate_number)
def init_ui(self, class_name, corrected_image, plate_number):
layout = QHBoxLayout()
layout.setContentsMargins(10, 5, 10, 5)
# 车牌类型标签
type_label = QLabel(class_name)
type_label.setFixedWidth(60)
type_label.setAlignment(Qt.AlignCenter)
type_label.setStyleSheet(
"QLabel { "
"background-color: #4CAF50 if class_name == '绿牌' else #2196F3; "
"color: white; "
"border-radius: 5px; "
"padding: 5px; "
"font-weight: bold; "
"}"
)
if class_name == '绿牌':
type_label.setStyleSheet(
"QLabel { "
"background-color: #4CAF50; "
"color: white; "
"border-radius: 5px; "
"padding: 5px; "
"font-weight: bold; "
"}"
)
else:
type_label.setStyleSheet(
"QLabel { "
"background-color: #2196F3; "
"color: white; "
"border-radius: 5px; "
"padding: 5px; "
"font-weight: bold; "
"}"
)
# 矫正后的车牌图像
image_label = QLabel()
image_label.setFixedSize(120, 40)
image_label.setStyleSheet("border: 1px solid #ddd; background-color: white;")
if corrected_image is not None:
# 转换numpy数组为QPixmap
h, w = corrected_image.shape[:2]
if len(corrected_image.shape) == 3:
bytes_per_line = 3 * w
q_image = QImage(corrected_image.data, w, h, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
else:
bytes_per_line = w
q_image = QImage(corrected_image.data, w, h, bytes_per_line, QImage.Format_Grayscale8)
pixmap = QPixmap.fromImage(q_image)
scaled_pixmap = pixmap.scaled(120, 40, Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
else:
image_label.setText("车牌未完全\n进入摄像头")
image_label.setAlignment(Qt.AlignCenter)
image_label.setStyleSheet("border: 1px solid #ddd; background-color: #f5f5f5; color: #666;")
# 车牌号标签
number_label = QLabel(plate_number)
number_label.setFixedWidth(150)
number_label.setAlignment(Qt.AlignCenter)
number_label.setStyleSheet(
"QLabel { "
"border: 1px solid #ddd; "
"background-color: white; "
"padding: 8px; "
"font-family: 'Courier New'; "
"font-size: 14px; "
"font-weight: bold; "
"}"
)
layout.addWidget(type_label)
layout.addWidget(image_label)
layout.addWidget(number_label)
layout.addStretch()
self.setLayout(layout)
self.setStyleSheet(
"QWidget { "
"background-color: white; "
"border: 1px solid #e0e0e0; "
"border-radius: 8px; "
"margin: 2px; "
"}"
)
class MainWindow(QMainWindow):
"""主窗口类"""
def __init__(self):
super().__init__()
self.detector = None
self.camera_thread = None
self.current_frame = None
self.detections = []
self.init_ui()
self.init_detector()
self.init_camera()
def init_ui(self):
"""初始化用户界面"""
self.setWindowTitle("车牌识别系统")
self.setGeometry(100, 100, 1200, 800)
# 创建中央widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建主布局
main_layout = QHBoxLayout(central_widget)
# 左侧摄像头显示区域
left_frame = QFrame()
left_frame.setFrameStyle(QFrame.StyledPanel)
left_frame.setStyleSheet("QFrame { background-color: #f0f0f0; border: 2px solid #ddd; }")
left_layout = QVBoxLayout(left_frame)
# 摄像头显示标签
self.camera_label = QLabel()
self.camera_label.setMinimumSize(640, 480)
self.camera_label.setStyleSheet("QLabel { background-color: black; border: 1px solid #ccc; }")
self.camera_label.setAlignment(Qt.AlignCenter)
self.camera_label.setText("摄像头未启动")
self.camera_label.setScaledContents(True)
# 控制按钮
button_layout = QHBoxLayout()
self.start_button = QPushButton("启动摄像头")
self.stop_button = QPushButton("停止摄像头")
self.start_button.clicked.connect(self.start_camera)
self.stop_button.clicked.connect(self.stop_camera)
self.stop_button.setEnabled(False)
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.stop_button)
button_layout.addStretch()
left_layout.addWidget(self.camera_label)
left_layout.addLayout(button_layout)
# 右侧结果显示区域
right_frame = QFrame()
right_frame.setFrameStyle(QFrame.StyledPanel)
right_frame.setFixedWidth(400)
right_frame.setStyleSheet("QFrame { background-color: #fafafa; border: 2px solid #ddd; }")
right_layout = QVBoxLayout(right_frame)
# 标题
title_label = QLabel("检测结果")
title_label.setAlignment(Qt.AlignCenter)
title_label.setFont(QFont("Arial", 16, QFont.Bold))
title_label.setStyleSheet("QLabel { color: #333; padding: 10px; }")
# 车牌数量显示
self.count_label = QLabel("识别到的车牌数量: 0")
self.count_label.setAlignment(Qt.AlignCenter)
self.count_label.setFont(QFont("Arial", 12))
self.count_label.setStyleSheet(
"QLabel { "
"background-color: #e3f2fd; "
"border: 1px solid #2196f3; "
"border-radius: 5px; "
"padding: 8px; "
"color: #1976d2; "
"font-weight: bold; "
"}"
)
# 滚动区域用于显示车牌结果
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setStyleSheet("QScrollArea { border: none; background-color: transparent; }")
self.results_widget = QWidget()
self.results_layout = QVBoxLayout(self.results_widget)
self.results_layout.setAlignment(Qt.AlignTop)
scroll_area.setWidget(self.results_widget)
right_layout.addWidget(title_label)
right_layout.addWidget(self.count_label)
right_layout.addWidget(scroll_area)
# 添加到主布局
main_layout.addWidget(left_frame, 2)
main_layout.addWidget(right_frame, 1)
# 设置样式
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QPushButton {
background-color: #2196F3;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #1976D2;
}
QPushButton:pressed {
background-color: #0D47A1;
}
QPushButton:disabled {
background-color: #cccccc;
color: #666666;
}
""")
def init_detector(self):
"""初始化检测器"""
model_path = os.path.join(os.path.dirname(__file__), "yolopart", "yolo11s-pose42.pt")
self.detector = LicensePlateYOLO(model_path)
def init_camera(self):
"""初始化摄像头线程"""
self.camera_thread = CameraThread()
self.camera_thread.frame_ready.connect(self.process_frame)
def start_camera(self):
"""启动摄像头"""
if self.camera_thread.start_camera():
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.camera_label.setText("摄像头启动中...")
else:
self.camera_label.setText("摄像头启动失败")
def stop_camera(self):
"""停止摄像头"""
self.camera_thread.stop_camera()
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.camera_label.setText("摄像头已停止")
self.camera_label.clear()
def process_frame(self, frame):
"""处理摄像头帧"""
self.current_frame = frame.copy()
# 进行车牌检测
self.detections = self.detector.detect_license_plates(frame)
# 在图像上绘制检测结果
display_frame = self.draw_detections(frame.copy())
# 转换为Qt格式并显示
self.display_frame(display_frame)
# 更新右侧结果显示
self.update_results_display()
def draw_detections(self, frame):
"""在图像上绘制检测结果"""
return self.detector.draw_detections(frame, self.detections)
def display_frame(self, frame):
"""显示帧到界面"""
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_frame.shape
bytes_per_line = ch * w
qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(qt_image)
scaled_pixmap = pixmap.scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.camera_label.setPixmap(scaled_pixmap)
def update_results_display(self):
"""更新右侧结果显示"""
# 更新车牌数量
count = len(self.detections)
self.count_label.setText(f"识别到的车牌数量: {count}")
# 清除之前的结果
for i in reversed(range(self.results_layout.count())):
child = self.results_layout.itemAt(i).widget()
if child:
child.setParent(None)
# 添加新的结果
for i, detection in enumerate(self.detections):
# 矫正车牌图像
corrected_image = self.correct_license_plate(detection)
# 获取车牌号(占位)
plate_number = self.recognize_plate_number(corrected_image)
# 创建车牌显示组件
plate_widget = LicensePlateWidget(
i + 1,
detection['class_name'],
corrected_image,
plate_number
)
self.results_layout.addWidget(plate_widget)
def correct_license_plate(self, detection):
"""矫正车牌图像"""
if self.current_frame is None:
return None
# 检查是否为不完整检测
if detection.get('incomplete', False):
return None
# 使用检测器的矫正方法
return self.detector.correct_license_plate(
self.current_frame,
detection['keypoints']
)
def recognize_plate_number(self, corrected_image):
"""识别车牌号"""
if corrected_image is None:
return "识别失败"
try:
# 使用OCR接口进行识别
# 可以根据需要切换为CRNN: crnn_predict(corrected_image)
result = ocr_predict(corrected_image)
# 将字符列表转换为字符串
if isinstance(result, list) and len(result) >= 7:
return ''.join(result[:7])
else:
return "识别失败"
except Exception as e:
print(f"车牌号识别失败: {e}")
return "识别失败"
def closeEvent(self, event):
"""窗口关闭事件"""
if self.camera_thread:
self.camera_thread.stop_camera()
event.accept()
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

211
oled_fonts.h Normal file
View File

@@ -0,0 +1,211 @@
// 字库头文件
// 定义条件编译宏,防止头文件的重复包含和编译
#ifndef OLOED_FONTS_H
#define OLOED_FONTS_H
/************************************6*8的点阵************************************/
// 取模方式纵向8点下高位
// 采用N*6的二维数组
// 第一维表示字符
// 每个字符对应第二维的6个数组元素每个数组元素1字节表示1列像素一共6列8行
static unsigned char F6x8[][6] =
{
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 空格
{ 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // !
{ 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
{ 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // #
{ 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $
{ 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // %
{ 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
{ 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
{ 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // (
{ 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // )
{ 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // *
{ 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
{ 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // ,
{ 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // .
{ 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
{ 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0
{ 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
{ 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2
{ 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3
{ 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
{ 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
{ 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
{ 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
{ 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
{ 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
{ 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
{ 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 }, // ;
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
{ 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
{ 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 }, // >
{ 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
{ 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E }, // @
{ 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C }, // A
{ 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
{ 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
{ 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
{ 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
{ 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
{ 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G
{ 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
{ 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I
{ 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
{ 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
{ 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
{ 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
{ 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
{ 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O
{ 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P
{ 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
{ 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
{ 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 }, // S
{ 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
{ 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
{ 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
{ 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W
{ 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
{ 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y
{ 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
{ 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [
{ 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 }, /* \ */
{ 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ]
{ 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
{ 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 }, // _
{ 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 }, // '
{ 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
{ 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b
{ 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 }, // c
{ 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
{ 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
{ 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f
{ 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g
{ 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
{ 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i
{ 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 }, // j
{ 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
{ 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l
{ 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
{ 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n
{ 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 }, // o
{ 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 }, // p
{ 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC }, // q
{ 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r
{ 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 }, // s
{ 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t
{ 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u
{ 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
{ 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
{ 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
{ 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C }, // y
{ 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 }, // horiz lines
};
/****************************************8*16的点阵************************************/
// 取模方式纵向8点下高位
// 采用一维数组每个字符对应16个数组元素
// 每16个数组元素的前8个表示字符的上半部分8*8点阵后8个表示字符的下半部分8*8点阵一共8列16行
static const unsigned char F8X16[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//空格 0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};
#endif

294
oled_ssd1306.c Normal file
View File

@@ -0,0 +1,294 @@
// OLED显示屏简化版驱动源文件
#include <stdio.h> // 标准输入输出
#include <stddef.h> // 标准类型定义
#include "iot_gpio.h" // OpenHarmony HALIoT硬件设备操作接口-GPIO
#include "iot_i2c.h" // OpenHarmony HALIoT硬件设备操作接口-I2C
#include "iot_errno.h" // OpenHarmony HALIoT硬件设备操作接口-错误代码定义
#include "hi_io.h" // 海思 Pegasus SDKIoT硬件设备操作接口-IO
// 字库头文件
#include "oled_fonts.h"
// OLED显示屏简化版驱动接口文件
#include "oled_ssd1306.h"
// 定义一个宏,用于计算数组的长度
#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])
// 定义一个宏用于标识I2C0
#define OLED_I2C_IDX 0
// 定义一个宏用于标识I2C0的波特率传输速率
#define OLED_I2C_BAUDRATE (400 * 1000) // 400KHz
// 定义一个宏用于标识OLED的宽度
#define OLED_WIDTH (128)
// 定义一个宏用于标识SSD1306显示屏驱动芯片的设备地址
#define OLED_I2C_ADDR 0x78
// 定义一个宏,用于标识写命令操作
#define OLED_I2C_CMD 0x00 // 0000 0000 写命令
// 定义一个宏,用于标识写数据操作
#define OLED_I2C_DATA 0x40 // 0100 0000(0x40) 写数据
// 定义一个宏用于标识100ms的延时
#define DELAY_100_MS (100 * 1000)
// 定义一个结构体,表示要发送或接收的数据
typedef struct
{
// 要发送的数据的指针
unsigned char *sendBuf;
// 要发送的数据长度
unsigned int sendLen;
// 要接收的数据的指针
unsigned char *receiveBuf;
// 要接收的数据长度
unsigned int receiveLen;
} IotI2cData;
/// @brief 向OLED写一个字节
/// @param regAddr 写入命令还是数据 OLED_I2C_CMD / OLED_I2C_DATA
/// @param byte 写入的内容
/// @retval 成功返回IOT_SUCCESS失败返回IOT_FAILURE
static uint32_t I2cWiteByte(uint8_t regAddr, uint8_t byte)
{
// 定义字节流
uint8_t buffer[] = {regAddr, byte};
IotI2cData i2cData = {0};
i2cData.sendBuf = buffer;
i2cData.sendLen = sizeof(buffer) / sizeof(buffer[0]);
// 发送字节流
return IoTI2cWrite(OLED_I2C_IDX, OLED_I2C_ADDR, i2cData.sendBuf, i2cData.sendLen);
}
/// @brief 向OLED写一个命令字节
/// @param cmd 写入的命令字节
/// @return 成功返回IOT_SUCCESS失败返回IOT_FAILURE
static uint32_t WriteCmd(uint8_t cmd)
{
return I2cWiteByte(OLED_I2C_CMD, cmd);
}
/// @brief 向OLED写一个数据字节
/// @param cmd 写入的数据字节
/// @return 成功返回IOT_SUCCESS失败返回IOT_FAILURE
uint32_t WriteData(uint8_t data)
{
return I2cWiteByte(OLED_I2C_DATA, data);
}
/// @brief 初始化SSD1306显示屏驱动芯片
uint32_t OledInit(void)
{
// 构造初始化代码
static const uint8_t initCmds[] = {
0xAE, // 显示关闭
0x00, // 页寻址模式时设置列地址的低4位为0000
0x10, // 页寻址模式时设置列地址的高4位为0000
0x40, // 设置起始行地址为第0行
0xB0, // 页寻址模式时设置页面起始地址为PAGE0
0x81, // 设置对比度
0xFF, // 对比度数值
0xA1, // set segment remap
0xA6, // 设置正常显示。0对应像素熄灭1对应像素亮起
0xA8, // --set multiplex ratio(1 to 64)
0x3F, // --1/32 duty
0xC8, // Com scan direction
0xD3, // -set display offset
0x00, //
0xD5, // set osc division
0x80, //
0xD8, // set area color mode off
0x05, //
0xD9, // Set Pre-Charge Period
0xF1, //
0xDA, // set com pin configuartion
0x12, //
0xDB, // set Vcomh
0x30, //
0x8D, // set charge pump enable
0x14, //
0xAF, // 显示开启
};
// 初始化GPIO-13
IoTGpioInit(HI_IO_NAME_GPIO_13);
// 设置GPIO-13引脚功能为I2C0_SDA
hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2C0_SDA);
// 初始化GPIO-14
IoTGpioInit(HI_IO_NAME_GPIO_14);
// 设置GPIO-14引脚功能为I2C0_SCL
hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_I2C0_SCL);
// 用指定的波特速率初始化I2C0
IoTI2cInit(OLED_I2C_IDX, OLED_I2C_BAUDRATE);
// 发送初始化代码初始化SSD1306显示屏驱动芯片
for (size_t i = 0; i < ARRAY_SIZE(initCmds); i++)
{
// 发送一个命令字节
uint32_t status = WriteCmd(initCmds[i]);
if (status != IOT_SUCCESS)
{
return status;
}
}
// OLED初始化完成返回成功
return IOT_SUCCESS;
}
/// @brief 设置显示位置
/// @param x x坐标1像素为单位
/// @param y y坐标8像素为单位。即页面起始地址
/// @return 无
void OledSetPosition(uint8_t x, uint8_t y)
{
//设置页面起始地址
WriteCmd(0xb0 + y);
// 列0~127
// 第0列0x00列二进制00000000。低地址0000即0x00。高地址0000(需要|0x10)0000|0x10=0x10。
// 第127列0x7f列二进制01111111。低地址1111即0x0F。高地址0111(需要|0x10)0111|0x10=0x17。
// 设置显示位置列地址的低4位
// 直接取出列地址低4位作为命令代码的低4位命令代码的高4位为0000
WriteCmd(x & 0x0f);
// 设置显示位置列地址的高4位
// 取出列地址高4位作为命令代码的低4位命令代码的高4位必须为0001
// 实际编程时列地址的高4位和0x10二进制00010000进行按位或即得到命令代码
WriteCmd(((x & 0xf0) >> 4) | 0x10);
}
/// @brief 全屏填充
/// @param fillData 填充的数据1字节
/// @return 无
void OledFillScreen(uint8_t fillData)
{
// 相关变量用于遍历page和列
uint8_t m = 0;
uint8_t n = 0;
// 写入所有页的数据
for (m = 0; m < 8; m++)
{
//设置页地址0~7
WriteCmd(0xb0 + m);
// 设置显示位置为第0列
WriteCmd(0x00); //设置显示位置:列低地址(0000)
WriteCmd(0x10); //设置显示位置:列高地址(0000)
// 写入128列数据
// 在一个页中数据按列写入一次一列对应发送过来的1字节数据
for (n = 0; n < 128; n++)
{
// 写入一个字节数据
WriteData(fillData);
}
}
}
/// @brief 清屏函数
/// @return 无
void OledClearScreen(void)
{
OledFillScreen(0x00); // 用0x00填充整个屏幕实现清屏
}
/// @brief 显示一个字符
/// @param x: x坐标1像素为单位
/// @param y: y坐标8像素为单位
/// @param ch: 要显示的字符
/// @param font: 字库
void OledShowChar(uint8_t x, uint8_t y, uint8_t ch, Font font)
{
// 数组下标
uint8_t c = 0;
// 循环控制
uint8_t i = 0;
// 得到数组下标
// 空格的ASCII码32在字库中的下标是0。字库中的字符-空格即相应的数组下标
c = ch - ' ';
// 显示字符
if (font == FONT8x16) // 8*16的点阵一个page放不下
{
// 显示字符的上半部分
// 设置显示位置
OledSetPosition(x, y);
// 逐个字节写入16个数组元素的前8个
for (i = 0; i < 8; i++)
{
WriteData(F8X16[c * 16 + i]);
}
// 显示字符的下半部分
// 设置显示位置为下一个PAGE
OledSetPosition(x, y + 1);
// 逐个字节写入16个数组元素的后8个
for (i = 0; i < 8; i++)
{
WriteData(F8X16[c * 16 + 8 + i]);
}
}
else // 6*8的点阵在一个page中
{
// 设置显示位置
OledSetPosition(x, y);
// 逐个字节写入数组第二维的6个数组元素
for (i = 0; i < 6; i++)
{
WriteData(F6x8[c][i]);
}
}
}
/// @brief 显示一个字符串
/// @param x: x坐标1像素为单位
/// @param y: y坐标8像素为单位
/// @param str: 要显示的字符串
/// @param font: 字库
void OledShowString(uint8_t x, uint8_t y, const char *str, Font font)
{
// 字符数组(字符串)下标
uint8_t j = 0;
// 检查字符串是否为空
if (str == NULL)
{
printf("param is NULL,Please check!!!\r\n");
return;
}
// 遍历字符串,显示每个字符
while (str[j])
{
// 显示一个字符
OledShowChar(x, y, str[j], font);
// 设置字符间距
x += 8;
// 如果下一个要显示的字符超出了OLED显示的范围则换行
if (x > 120)
{
x = 0;
y += 2;
}
// 下一个字符
j++;
}
}

32
oled_ssd1306.h Normal file
View File

@@ -0,0 +1,32 @@
// OLED显示屏简化版驱动接口文件
// 定义条件编译宏,防止头文件的重复包含和编译
#ifndef OLED_SSD1306_H
#define OLED_SSD1306_H
#include <stdint.h> // 定义了几种扩展的整数类型和宏
// 声明接口函数
uint32_t OledInit(void);
void OledSetPosition(uint8_t x, uint8_t y);
void OledFillScreen(uint8_t fillData);
uint32_t WriteData(uint8_t data);
// 清屏函数
void OledClearScreen(void);
// 定义字库类型
enum Font {
FONT6x8 = 1,
FONT8x16
};
typedef enum Font Font;
// 声明接口函数
void OledShowChar(uint8_t x, uint8_t y, uint8_t ch, Font font);
void OledShowString(uint8_t x, uint8_t y, const char* str, Font font);
// 条件编译结束
#endif // OLED_SSD1306_H

View File

@@ -1,20 +0,0 @@
# 车牌识别系统依赖包
# 深度学习和计算机视觉
ultralytics>=8.0.0
opencv-python>=4.5.0
numpy>=1.21.0
# PyQt5界面
PyQt5>=5.15.0
# 图像处理
Pillow>=8.0.0
# 可选如果需要GPU加速
# torch>=1.9.0
# torchvision>=0.10.0
# 可选:如果需要其他功能
# matplotlib>=3.3.0 # 用于调试和可视化
# scipy>=1.7.0 # 科学计算

69
robot_sg90.c Normal file
View File

@@ -0,0 +1,69 @@
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"
#include "hi_io.h"
#include "hi_time.h"
//查阅机器人板原理图可知SG90舵机通过GPIO2与3861连接
//SG90舵机的控制需要MCU产生一个周期为20ms的脉冲信号以0.5ms到2.5ms的高电平来控制舵机转动的角度
#define GPIO2 2
//输出20000微秒的脉冲信号(x微秒高电平,20000-x微秒低电平)
void set_angle( unsigned int duty) {
IoTGpioSetDir(GPIO2, IOT_GPIO_DIR_OUT);//设置GPIO2为输出模式
//GPIO2输出x微秒高电平
IoTGpioSetOutputVal(GPIO2, IOT_GPIO_VALUE1);
hi_udelay(duty);
//GPIO2输出20000-x微秒低电平
IoTGpioSetOutputVal(GPIO2, IOT_GPIO_VALUE0);
hi_udelay(20000 - duty);
}
/*Steering gear turn left (counter-clockwise 90 degrees)
1、依据角度与脉冲的关系设置高电平时间为1000微秒
2、发送20次脉冲信号控制舵机逆时针旋转90度
*/
void engine_turn_left(void)
{
for (int i = 0; i < 20; i++) {
set_angle(1000);
}
}
/*Steering gear turn right (clockwise 90 degrees)
1、依据角度与脉冲的关系设置高电平时间为2000微秒
2、发送20次脉冲信号控制舵机顺时针旋转90度
*/
void engine_turn_right(void)
{
for (int i = 0; i < 20; i++) {
set_angle(2000);
}
}
/*Steering gear return to middle
1、依据角度与脉冲的关系设置高电平时间为1500微秒
2、发送20次脉冲信号控制舵机居中
*/
void regress_middle(void)
{
for (int i = 0; i < 20; i++) {
set_angle(1500);
}
}
// 顺时针旋转90度
void servo_rotate_clockwise_90(void) {
engine_turn_right();
}
// 逆时针旋转90度
void servo_rotate_counter_clockwise_90(void) {
engine_turn_left();
}

13
robot_sg90.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef ROBOT_SG90_H
#define ROBOT_SG90_H
void set_angle(int angle);
void engine_turn_left(void);
void engine_turn_right(void);
void regress_middle(void);
// 新增的90度精确旋转函数
void servo_rotate_clockwise_90(void);
void servo_rotate_counter_clockwise_90(void);
#endif

53
simple_client.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简单的UDP客户端程序
向Hi3861设备发送JSON命令
"""
import socket
import json
import time
def send_command():
"""发送命令到Hi3861设备"""
# 目标设备信息
target_ip = "192.168.43.12"
target_port = 8081
# 创建JSON命令
command = {
"cmd": 1, # 测试命令4只显示字符串舵机不动
"text": "沪AAAAAA 通行 2sec"
}
json_command = json.dumps(command, ensure_ascii=False)
try:
# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发送命令
print(f"正在向 {target_ip}:{target_port} 发送命令...")
print(f"命令内容: {json_command}")
sock.sendto(json_command.encode('utf-8'), (target_ip, target_port))
print("命令发送成功!")
print("设备将执行以下操作:")
print("1. 顺时针旋转舵机90度")
print("2. 在OLED屏幕上显示沪AAAAAA")
print("3. 等待10秒")
print("4. 逆时针旋转舵机90度")
print("5. 清空OLED屏幕")
except Exception as e:
print(f"发送命令失败: {e}")
finally:
sock.close()
if __name__ == "__main__":
print("Hi3861 简单客户端程序")
print("=" * 30)
send_command()
print("程序结束")

56
test_json.c Normal file
View File

@@ -0,0 +1,56 @@
#include <stdio.h>
#include <string.h>
#include "json_parser.h"
// 测试JSON解析功能
void test_json_parsing() {
printf("=== JSON解析测试 ===\n");
// 测试命令1旋转+显示+清屏
char test1[] = "{\"cmd\":1,\"text\":\"Hello World\"}";
JsonCommand cmd1;
if (ParseJsonCommand(test1, &cmd1) == 0) {
printf("测试1成功: cmd=%d, text=%s\n", cmd1.cmd, cmd1.text);
} else {
printf("测试1失败\n");
}
// 测试命令2顺时针旋转
char test2[] = "{\"cmd\":2,\"text\":\"\"}";
JsonCommand cmd2;
if (ParseJsonCommand(test2, &cmd2) == 0) {
printf("测试2成功: cmd=%d, text=%s\n", cmd2.cmd, cmd2.text);
} else {
printf("测试2失败\n");
}
// 测试命令3逆时针旋转
char test3[] = "{\"cmd\":3,\"text\":\"ignored\"}";
JsonCommand cmd3;
if (ParseJsonCommand(test3, &cmd3) == 0) {
printf("测试3成功: cmd=%d, text=%s\n", cmd3.cmd, cmd3.text);
} else {
printf("测试3失败\n");
}
// 测试IP消息创建
printf("\n=== IP消息创建测试 ===\n");
char ip_msg[128];
CreateIpMessage("192.168.1.100", ip_msg, sizeof(ip_msg));
printf("IP消息: %s\n", ip_msg);
}
// 测试舵机控制逻辑
void test_servo_commands() {
printf("\n=== 舵机命令测试 ===\n");
printf("命令1: 顺时针90°+显示+10秒后逆时针90°+清屏\n");
printf("命令2: 顺时针90°\n");
printf("命令3: 逆时针90°\n");
}
int main() {
test_json_parsing();
test_servo_commands();
return 0;
}

242
udp_client_test.c Normal file
View File

@@ -0,0 +1,242 @@
#include <stdio.h> // 标准输入输出
#include <unistd.h> // POSIX标准接口
#include <errno.h> // 错误码
#include <string.h> // 字符串处理(操作字符数组)
#include "lwip/sockets.h" // lwIP TCP/IP协议栈Socket API
#include "ohos_init.h" // 用于初始化服务(services)和功能(features)
#include "cmsis_os2.h" // CMSIS-RTOS API V2
#include "oled_ssd1306.h"
#include "json_parser.h" // JSON解析器
#include "wifi_connecter.h" // WiFi连接器
extern int control_flag ;
// 要发送的数据
static char request[] = "connecting";
// 要接收的数据
char response[128] = "";
// 全局变量存储解析后的命令
JsonCommand g_current_command = {0};
// 发送IP地址到服务器
void SendIpToServer(const char *host, unsigned short port) {
char ip_buffer[32] = {0};
char message_buffer[128] = {0};
// 获取本机IP地址
if (GetLocalIpAddress(ip_buffer, sizeof(ip_buffer)) != 0) {
printf("Failed to get local IP address\r\n");
return;
}
// 创建IP消息
if (CreateIpMessage(message_buffer, sizeof(message_buffer), ip_buffer) != 0) {
printf("Failed to create IP message\r\n");
return;
}
// 创建UDP socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("Failed to create socket\r\n");
return;
}
struct sockaddr_in toAddr = {0};
toAddr.sin_family = AF_INET;
toAddr.sin_port = htons(port);
if (inet_pton(AF_INET, host, &toAddr.sin_addr) <= 0) {
printf("inet_pton failed!\r\n");
lwip_close(sockfd);
return;
}
// 发送IP地址消息
ssize_t retval = sendto(sockfd, message_buffer, strlen(message_buffer), 0,
(struct sockaddr *)&toAddr, sizeof(toAddr));
if (retval < 0) {
printf("Failed to send IP message!\r\n");
} else {
printf("IP message sent: %s\r\n", message_buffer);
}
lwip_close(sockfd);
}
/// @brief UDP客户端测试函数
/// @param host UDP服务端IP地址
/// @param port UDP服务端端口
void UdpClientTest(const char *host, unsigned short port)
{
// 用于接收Socket API接口返回值
ssize_t retval = 0;
// 创建一个UDP Socket返回值为文件描述符
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("创建socket失败\r\n");
return;
}
// 用于设置服务端的地址信息
struct sockaddr_in toAddr = {0};
// 用于设置本地绑定的地址信息
struct sockaddr_in localAddr = {0};
// 使用IPv4协议
toAddr.sin_family = AF_INET;
localAddr.sin_family = AF_INET;
// 端口号,从主机字节序转为网络字节序
toAddr.sin_port = htons(port);
localAddr.sin_port = htons(port); // 绑定到相同端口接收数据
localAddr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的网络接口
// 将服务端IP地址从"点分十进制"字符串转化为标准格式32位整数
if (inet_pton(AF_INET, host, &toAddr.sin_addr) <= 0)
{
// 转化失败
printf("inet_pton failed!\r\n"); // 输出日志
lwip_close(sockfd);
return;
}
// 绑定本地端口,这样才能接收数据
printf("绑定本地端口 %d...\r\n", port);
if (bind(sockfd, (struct sockaddr *)&localAddr, sizeof(localAddr)) < 0) {
printf("绑定端口失败!错误: %d\r\n", errno);
lwip_close(sockfd);
return;
}
printf("端口绑定成功!\r\n");
// 发送设备IP地址到服务器
SendIpToServer(sockfd, &toAddr);
// 发送数据
// UDP socket是 "无连接的",因此每次发送都必须先指定目标主机和端口,主机可以是多播地址
// 发送数据的时候使用本地随机端口N
//
// 参数:
// ssocket文件描述符
// dataptr要发送的数据
// size要发送的数据的长度最大65332字节
// flags消息传输标志位
// to目标的地址信息
// tolen目标的地址信息长度
//
// 返回值:
// 发送的字节数,如果出错,返回-1
printf("发送连接消息到服务器...\r\n");
retval = sendto(sockfd, request, sizeof(request), 0, (struct sockaddr *)&toAddr, sizeof(toAddr));
// 检查接口返回值小于0表示发送失败
if (retval < 0)
{
// 发送失败
printf("发送消息失败!错误: %d\r\n", errno); // 输出日志
lwip_close(sockfd);
return;
}
// 发送成功
// 输出日志
printf("发送UDP消息成功: {%s} %ld 字节\r\n", request, retval);
// 显示等待接收状态
printf("开始监听UDP消息等待来自 %s 的数据...\r\n", host);
// 用于记录发送方的地址信息(IP地址和端口号)
struct sockaddr_in fromAddr = {0};
// 用于记录发送方的地址信息长度
socklen_t fromLen = sizeof(fromAddr);
// 在本地随机端口N上面接收数据
// UDP socket是 “无连接的”因此每次接收时并不知道消息来自何处通过fromAddr参数可以得到发送方的信息主机、端口号
// device\hisilicon\hispark_pegasus\sdk_liteos\third_party\lwip_sack\include\lwip\sockets.h -> lwip_recvfrom
//
// 参数:
// ssocket文件描述符
// buffer接收数据的缓冲区的地址
// length接收数据的缓冲区的长度
// flags消息接收标志位
// address发送方的地址信息
// address_len发送方的地址信息长度
//
// 返回值:
// 接收的字节数,如果出错,返回-1
while (1)
{
// 清空接收缓冲区
memset(response, 0, sizeof(response));
retval = lwip_recvfrom(sockfd, response, sizeof(response) - 1, 0, (struct sockaddr *)&fromAddr, &fromLen);
// 检查接口返回值小于0表示接收失败
if (retval <= 0)
{
// 接收失败或者收到0长度的数据(忽略掉)
printf("recvfrom failed or abort, %ld, %d!\r\n", retval, errno); // 输出日志
continue; // 继续接收不要关闭socket
}
// 接收成功
// 末尾添加字符串结束符'\0',以便后续的字符串操作
response[retval] = '\0';
// 输出日志 - 显示所有收到的消息
printf("=== 收到UDP消息 ===\r\n");
printf("消息内容: {%s}\r\n", response);
printf("消息长度: %ld 字节\r\n", retval);
printf("发送方IP: %s\r\n", inet_ntoa(fromAddr.sin_addr));
printf("发送方端口: %d\r\n", ntohs(fromAddr.sin_port));
printf("==================\r\n");
// 尝试解析JSON命令
JsonCommand temp_cmd = {0};
if (ParseJsonCommand(response, &temp_cmd) == 0) {
printf("JSON解析成功: cmd=%d, text=%s\r\n", temp_cmd.cmd, temp_cmd.text);
// 保存解析的命令
g_current_command = temp_cmd;
// 根据命令类型设置控制标志
if (temp_cmd.cmd >= 1 && temp_cmd.cmd <= 4) {
control_flag = temp_cmd.cmd;
printf("设置控制标志为: %d\r\n", control_flag);
} else {
printf("无效的命令类型: %d\r\n", temp_cmd.cmd);
}
} else {
printf("JSON解析失败尝试旧格式命令\r\n");
// 兼容旧的字符串命令格式
if(strlen(response) > 10) {
control_flag = 2;
g_current_command.cmd = 0; // 标记为旧命令
printf("设置为复杂字符串命令,控制标志: %d\r\n", control_flag);
} else if(strcmp(response,"T:on") == 0) {
control_flag = 1;
g_current_command.cmd = 0; // 标记为旧命令
printf("设置为开启命令,控制标志: %d\r\n", control_flag);
} else if(strcmp(response,"T:off") == 0) {
control_flag = 0;
g_current_command.cmd = 0; // 标记为旧命令
printf("设置为关闭命令,控制标志: %d\r\n", control_flag);
} else {
printf("未识别的命令格式: %s\r\n", response);
}
}
sleep(1);
}
// 关闭socket
lwip_close(sockfd);
}

146
wifi_connecter.c Normal file
View File

@@ -0,0 +1,146 @@
#include "cmsis_os2.h" // CMSIS-RTOS API V2
#include "wifi_device.h" // Wi-Fi设备接口station模式
#include "lwip/netifapi.h" // lwIP TCP/IP协议栈网络接口API
#include "lwip/api_shell.h" // lwIP TCP/IP协议栈SHELL命令API
static void PrintLinkedInfo(WifiLinkedInfo* info)
{
if (!info) return;
static char macAddress[32] = {0};
unsigned char* mac = info->bssid;
snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
printf("bssid: %s, rssi: %d, connState: %d, reason: %d, ssid: %s\r\n",
macAddress, info->rssi, info->connState, info->disconnectedReason, info->ssid);
}
static volatile int g_connected = 0;
static void OnWifiConnectionChanged(int state, WifiLinkedInfo* info)
{
if (!info) return;
printf("%s %d, state = %d, info = \r\n", __FUNCTION__, __LINE__, state);
PrintLinkedInfo(info);
if (state == WIFI_STATE_AVALIABLE) {
g_connected = 1;
} else {
g_connected = 0;
}
}
static void OnWifiScanStateChanged(int state, int size)
{
printf("%s %d, state = %X, size = %d\r\n", __FUNCTION__, __LINE__, state, size);
}
static WifiEvent g_defaultWifiEventListener = {
.OnWifiConnectionChanged = OnWifiConnectionChanged,
.OnWifiScanStateChanged = OnWifiScanStateChanged
};
static struct netif* g_iface = NULL;
err_t netifapi_set_hostname(struct netif *netif, char *hostname, u8_t namelen);
int ConnectToHotspot(WifiDeviceConfig* apConfig)
{
WifiErrorCode errCode;
int netId = -1;
errCode = RegisterWifiEvent(&g_defaultWifiEventListener);
printf("RegisterWifiEvent: %d\r\n", errCode);
errCode = EnableWifi();
printf("EnableWifi: %d\r\n", errCode);
errCode = AddDeviceConfig(apConfig, &netId);
printf("AddDeviceConfig: %d\r\n", errCode);
g_connected = 0;
errCode = ConnectTo(netId);
printf("ConnectTo(%d): %d\r\n", netId, errCode);
while (!g_connected) { // wait until connect to AP
// printf("connecting\n");
osDelay(10);
}
printf("g_connected: %d\r\n", g_connected);
g_iface = netifapi_netif_find("wlan0");
if (g_iface) {
err_t ret = 0;
char* hostname = "hispark";
ret = netifapi_set_hostname(g_iface, hostname, strlen(hostname));
printf("netifapi_set_hostname: %d\r\n", ret);
ret = netifapi_dhcp_start(g_iface);
printf("netifapi_dhcp_start: %d\r\n", ret);
osDelay(100); // wait DHCP server give me IP
#if 0
ret = netifapi_netif_common(g_iface, dhcp_clients_info_show, NULL);
printf("netifapi_netif_common: %d\r\n", ret);
#else
// 下面这种方式也可以打印 IP、网关、子网掩码信息
ip4_addr_t ip = {0};
ip4_addr_t netmask = {0};
ip4_addr_t gw = {0};
ret = netifapi_netif_get_addr(g_iface, &ip, &netmask, &gw);
if (ret == ERR_OK) {
printf("ip = %s\r\n", ip4addr_ntoa(&ip));
printf("netmask = %s\r\n", ip4addr_ntoa(&netmask));
printf("gw = %s\r\n", ip4addr_ntoa(&gw));
}
printf("netifapi_netif_get_addr: %d\r\n", ret);
#endif
}
return netId;
}
void DisconnectWithHotspot(int netId)
{
if (g_iface) {
err_t ret = netifapi_dhcp_stop(g_iface);
printf("netifapi_dhcp_stop: %d\r\n", ret);
}
WifiErrorCode errCode = Disconnect(); // disconnect with your AP
printf("Disconnect: %d\r\n", errCode);
errCode = UnRegisterWifiEvent(&g_defaultWifiEventListener);
printf("UnRegisterWifiEvent: %d\r\n", errCode);
RemoveDevice(netId); // remove AP config
printf("RemoveDevice: %d\r\n", errCode);
errCode = DisableWifi();
printf("DisableWifi: %d\r\n", errCode);
}
// 获取本机IP地址
int GetLocalIpAddress(char* ip_buffer, int buffer_size)
{
if (!ip_buffer || buffer_size < 16 || !g_iface) {
return -1;
}
ip4_addr_t ip = {0};
ip4_addr_t netmask = {0};
ip4_addr_t gw = {0};
err_t ret = netifapi_netif_get_addr(g_iface, &ip, &netmask, &gw);
if (ret == ERR_OK) {
const char* ip_str = ip4addr_ntoa(&ip);
if (ip_str && strlen(ip_str) < buffer_size) {
strcpy(ip_buffer, ip_str);
return 0;
}
}
return -1;
}

13
wifi_connecter.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef WIFI_CONNECTER_H
#define WIFI_CONNECTER_H
#include "wifi_device.h" // Wi-Fi设备接口station模式
int ConnectToHotspot(WifiDeviceConfig* apConfig);
void DisconnectWithHotspot(int netId);
// 获取本机IP地址
int GetLocalIpAddress(char* ip_buffer, int buffer_size);
#endif // WIFI_CONNECTER_H

View File

@@ -1,275 +0,0 @@
import cv2
import numpy as np
from ultralytics import YOLO
import os
class LicensePlateYOLO:
"""
车牌YOLO检测器类
负责加载YOLO pose模型并进行车牌检测和角点提取
"""
def __init__(self, model_path=None):
"""
初始化YOLO检测器
参数:
model_path: 模型文件路径如果为None则使用默认路径
"""
self.model = None
self.model_path = model_path or self._get_default_model_path()
self.class_names = {0: '蓝牌', 1: '绿牌'}
self.load_model()
def _get_default_model_path(self):
"""获取默认模型路径"""
current_dir = os.path.dirname(__file__)
return os.path.join(current_dir, "yolo11s-pose42.pt")
def load_model(self):
"""
加载YOLO pose模型
返回:
bool: 加载是否成功
"""
try:
if os.path.exists(self.model_path):
self.model = YOLO(self.model_path)
print(f"YOLO模型加载成功: {self.model_path}")
return True
else:
print(f"模型文件不存在: {self.model_path}")
return False
except Exception as e:
print(f"YOLO模型加载失败: {e}")
return False
def detect_license_plates(self, image, conf_threshold=0.5):
"""
检测图像中的车牌
参数:
image: 输入图像 (numpy数组)
conf_threshold: 置信度阈值
返回:
list: 检测结果列表,每个元素包含:
- box: 边界框坐标 [x1, y1, x2, y2]
- keypoints: 四个角点坐标 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
- confidence: 置信度
- class_id: 类别ID (0=蓝牌, 1=绿牌)
- class_name: 类别名称
"""
if self.model is None:
print("模型未加载")
return []
try:
# 进行推理
results = self.model(image, conf=conf_threshold, verbose=False)
detections = []
for result in results:
# 检查是否有检测结果
if result.boxes is None or result.keypoints is None:
continue
# 提取检测信息
boxes = result.boxes.xyxy.cpu().numpy() # 边界框
keypoints = result.keypoints.xy.cpu().numpy() # 关键点
confidences = result.boxes.conf.cpu().numpy() # 置信度
classes = result.boxes.cls.cpu().numpy() # 类别
# 处理每个检测结果
for i in range(len(boxes)):
# 检查关键点数量是否为4个
if len(keypoints[i]) == 4:
class_id = int(classes[i])
detection = {
'box': boxes[i],
'keypoints': keypoints[i],
'confidence': confidences[i],
'class_id': class_id,
'class_name': self.class_names.get(class_id, '未知')
}
detections.append(detection)
else:
# 关键点不足4个记录但标记为不完整
class_id = int(classes[i])
detection = {
'box': boxes[i],
'keypoints': keypoints[i] if len(keypoints[i]) > 0 else [],
'confidence': confidences[i],
'class_id': class_id,
'class_name': self.class_names.get(class_id, '未知'),
'incomplete': True # 标记为不完整
}
detections.append(detection)
return detections
except Exception as e:
print(f"检测过程中出错: {e}")
return []
def draw_detections(self, image, detections):
"""
在图像上绘制检测结果
参数:
image: 输入图像
detections: 检测结果列表
返回:
numpy.ndarray: 绘制了检测结果的图像
"""
draw_image = image.copy()
for i, detection in enumerate(detections):
box = detection['box']
keypoints = detection['keypoints']
class_name = detection['class_name']
confidence = detection['confidence']
incomplete = detection.get('incomplete', False)
# 绘制边界框
x1, y1, x2, y2 = map(int, box)
# 根据车牌类型选择颜色
if class_name == '绿牌':
box_color = (0, 255, 0) # 绿色
elif class_name == '蓝牌':
box_color = (255, 0, 0) # 蓝色
else:
box_color = (128, 128, 128) # 灰色
cv2.rectangle(draw_image, (x1, y1), (x2, y2), box_color, 2)
# 绘制标签
label = f"{class_name} {confidence:.2f}"
if incomplete:
label += " (不完整)"
# 计算文本大小和位置
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.6
thickness = 2
(text_width, text_height), _ = cv2.getTextSize(label, font, font_scale, thickness)
# 绘制文本背景
cv2.rectangle(draw_image, (x1, y1 - text_height - 10),
(x1 + text_width, y1), box_color, -1)
# 绘制文本
cv2.putText(draw_image, label, (x1, y1 - 5),
font, font_scale, (255, 255, 255), thickness)
# 绘制关键点和连线
if len(keypoints) >= 4 and not incomplete:
# 四个角点完整,用黄色连线
points = [(int(kp[0]), int(kp[1])) for kp in keypoints[:4]]
# 绘制关键点
for point in points:
cv2.circle(draw_image, point, 5, (0, 255, 255), -1)
# 连接关键点形成四边形(按顺序连接)
# 假设关键点顺序为: right_bottom, left_bottom, left_top, right_top
for j in range(4):
cv2.line(draw_image, points[j], points[(j+1)%4], (0, 255, 255), 2)
elif len(keypoints) > 0:
# 关键点不完整,用红色标记现有点
for kp in keypoints:
point = (int(kp[0]), int(kp[1]))
cv2.circle(draw_image, point, 5, (0, 0, 255), -1)
return draw_image
def correct_license_plate(self, image, keypoints, target_size=(240, 80)):
"""
使用四个角点对车牌进行透视变换矫正
参数:
image: 原始图像
keypoints: 四个角点坐标
target_size: 目标尺寸 (width, height)
返回:
numpy.ndarray: 矫正后的车牌图像如果失败返回None
"""
if len(keypoints) != 4:
return None
try:
# 将关键点转换为numpy数组
src_points = np.array(keypoints, dtype=np.float32)
# 定义目标矩形的四个角点
# 假设关键点顺序为: right_bottom, left_bottom, left_top, right_top
# 重新排序为标准顺序: left_top, right_top, right_bottom, left_bottom
width, height = target_size
dst_points = np.array([
[0, 0], # left_top
[width, 0], # right_top
[width, height], # right_bottom
[0, height] # left_bottom
], dtype=np.float32)
# 重新排序源点以匹配目标点
# 原顺序: right_bottom, left_bottom, left_top, right_top
# 目标顺序: left_top, right_top, right_bottom, left_bottom
reordered_src = np.array([
src_points[2], # left_top
src_points[3], # right_top
src_points[0], # right_bottom
src_points[1] # left_bottom
], dtype=np.float32)
# 计算透视变换矩阵
matrix = cv2.getPerspectiveTransform(reordered_src, dst_points)
# 应用透视变换
corrected = cv2.warpPerspective(image, matrix, target_size)
return corrected
except Exception as e:
print(f"车牌矫正失败: {e}")
return None
def get_model_info(self):
"""
获取模型信息
返回:
dict: 模型信息字典
"""
if self.model is None:
return {"status": "未加载", "path": self.model_path}
return {
"status": "已加载",
"path": self.model_path,
"model_type": "YOLO11 Pose",
"classes": self.class_names
}
def initialize_yolo_detector(model_path=None):
"""
初始化YOLO检测器的便捷函数
参数:
model_path: 模型文件路径
返回:
LicensePlateYOLO: 初始化后的检测器实例
"""
detector = LicensePlateYOLO(model_path)
return detector
if __name__ == "__main__":
# 测试代码
detector = initialize_yolo_detector()
print("检测器信息:", detector.get_model_info())

Binary file not shown.