Compare commits
34 Commits
3d7c7a06e4
...
hi3861
| Author | SHA1 | Date | |
|---|---|---|---|
| 16d6108292 | |||
| 30edbda7de | |||
| 3d7a4dcb4d | |||
| 6c7f013a0c | |||
| 95aa6b6bba | |||
| 739cd1d914 | |||
| 01df759772 | |||
| cb88e6fccd | |||
| 80e995b47c | |||
| f82df06a68 | |||
| dc651af561 | |||
| 9f9bd25ce7 | |||
| 97ca0d75c2 | |||
| 75cc3b8ea3 | |||
| aca5703b9e | |||
| 2eba46bc40 | |||
| f342d37d63 | |||
| 1c914cf89f | |||
| afba7af80b | |||
| 8eef0d9414 | |||
| 8e8fda7fe9 | |||
| 9879cb1547 | |||
| 3829cf76ee | |||
| c8a541ec11 | |||
| b5839d2c36 | |||
| afe15b990a | |||
| 7f89965956 | |||
| c7ecc5325e | |||
| 01b286fce1 | |||
| 85c8302fc1 | |||
| 0cd70df215 | |||
| 658560c34f | |||
| c773a12f90 | |||
| a41a4a2236 |
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
12
.idea/License_plate_recognition.iml
generated
12
.idea/License_plate_recognition.iml
generated
@@ -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>
|
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -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
7
.idea/misc.xml
generated
@@ -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
8
.idea/modules.xml
generated
@@ -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
7
.idea/vcs.xml
generated
@@ -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
20
BUILD.gn
Normal 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接口
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
@@ -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
155
README.md
@@ -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
56
chinese_char_map.h
Normal 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
525
demo_entry_cmsis.c
Normal 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
64
display_helper.c
Normal 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
9
display_helper.h
Normal 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
72
json_parser.c
Normal 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
29
json_parser.h
Normal 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
411
main.py
@@ -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
211
oled_fonts.h
Normal 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
294
oled_ssd1306.c
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// OLED显示屏简化版驱动源文件
|
||||||
|
|
||||||
|
#include <stdio.h> // 标准输入输出
|
||||||
|
#include <stddef.h> // 标准类型定义
|
||||||
|
|
||||||
|
#include "iot_gpio.h" // OpenHarmony HAL:IoT硬件设备操作接口-GPIO
|
||||||
|
#include "iot_i2c.h" // OpenHarmony HAL:IoT硬件设备操作接口-I2C
|
||||||
|
#include "iot_errno.h" // OpenHarmony HAL:IoT硬件设备操作接口-错误代码定义
|
||||||
|
#include "hi_io.h" // 海思 Pegasus SDK:IoT硬件设备操作接口-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
32
oled_ssd1306.h
Normal 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
|
||||||
@@ -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
69
robot_sg90.c
Normal 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
13
robot_sg90.h
Normal 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
53
simple_client.py
Normal 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
56
test_json.c
Normal 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
242
udp_client_test.c
Normal 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
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// s:socket文件描述符
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// s:socket文件描述符
|
||||||
|
// 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
146
wifi_connecter.c
Normal 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
13
wifi_connecter.h
Normal 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
|
||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user