Compare commits
	
		
			3 Commits
		
	
	
		
			8ace9df86a
			...
			hi3861
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 16d6108292 | |||
| 30edbda7de | |||
| 3d7a4dcb4d | 
							
								
								
									
										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="cnm" 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="cnm" 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="$PROJECT_DIR$/.." 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接口
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -1,336 +0,0 @@
 | 
			
		||||
import torch
 | 
			
		||||
import torch.nn as nn
 | 
			
		||||
import torch.nn.functional as F
 | 
			
		||||
import numpy as np
 | 
			
		||||
from PIL import Image
 | 
			
		||||
import cv2
 | 
			
		||||
from torchvision import transforms
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
# 全局变量
 | 
			
		||||
crnn_model = None
 | 
			
		||||
crnn_decoder = None
 | 
			
		||||
crnn_preprocessor = None
 | 
			
		||||
device = None
 | 
			
		||||
 | 
			
		||||
class CRNN(nn.Module):
 | 
			
		||||
    """CRNN车牌识别模型"""
 | 
			
		||||
    def __init__(self, img_height=32, num_classes=68, hidden_size=256):
 | 
			
		||||
        super(CRNN, self).__init__()
 | 
			
		||||
        self.img_height = img_height
 | 
			
		||||
        self.num_classes = num_classes
 | 
			
		||||
        self.hidden_size = hidden_size
 | 
			
		||||
        
 | 
			
		||||
        # CNN特征提取部分 - 7层卷积
 | 
			
		||||
        self.cnn = nn.Sequential(
 | 
			
		||||
            # 第1层:3->64, 3x3卷积
 | 
			
		||||
            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
 | 
			
		||||
            nn.BatchNorm2d(64),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
            nn.MaxPool2d(kernel_size=2, stride=2),
 | 
			
		||||
            
 | 
			
		||||
            # 第2层:64->128, 3x3卷积
 | 
			
		||||
            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
 | 
			
		||||
            nn.BatchNorm2d(128),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
            nn.MaxPool2d(kernel_size=2, stride=2),
 | 
			
		||||
            
 | 
			
		||||
            # 第3层:128->256, 3x3卷积
 | 
			
		||||
            nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
 | 
			
		||||
            nn.BatchNorm2d(256),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
            
 | 
			
		||||
            # 第4层:256->256, 3x3卷积
 | 
			
		||||
            nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
 | 
			
		||||
            nn.BatchNorm2d(256),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
            nn.MaxPool2d(kernel_size=(2, 1), stride=(2, 1)),
 | 
			
		||||
            
 | 
			
		||||
            # 第5层:256->512, 3x3卷积
 | 
			
		||||
            nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1),
 | 
			
		||||
            nn.BatchNorm2d(512),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
            
 | 
			
		||||
            # 第6层:512->512, 3x3卷积
 | 
			
		||||
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
 | 
			
		||||
            nn.BatchNorm2d(512),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
            nn.MaxPool2d(kernel_size=(2, 1), stride=(2, 1)),
 | 
			
		||||
            
 | 
			
		||||
            # 第7层:512->512, 2x2卷积
 | 
			
		||||
            nn.Conv2d(512, 512, kernel_size=2, stride=1, padding=0),
 | 
			
		||||
            nn.BatchNorm2d(512),
 | 
			
		||||
            nn.ReLU(inplace=True),
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        # RNN序列建模部分 - 2层双向LSTM
 | 
			
		||||
        self.rnn = nn.LSTM(
 | 
			
		||||
            input_size=512,
 | 
			
		||||
            hidden_size=hidden_size,
 | 
			
		||||
            num_layers=2,
 | 
			
		||||
            batch_first=True,
 | 
			
		||||
            bidirectional=True
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        # 全连接分类层
 | 
			
		||||
        self.fc = nn.Linear(hidden_size * 2, num_classes)
 | 
			
		||||
        
 | 
			
		||||
    def forward(self, x):
 | 
			
		||||
        batch_size = x.size(0)
 | 
			
		||||
        
 | 
			
		||||
        # CNN特征提取
 | 
			
		||||
        conv_out = self.cnn(x)
 | 
			
		||||
        
 | 
			
		||||
        # 重塑为RNN输入格式
 | 
			
		||||
        batch_size, channels, height, width = conv_out.size()
 | 
			
		||||
        conv_out = conv_out.permute(0, 3, 1, 2)
 | 
			
		||||
        conv_out = conv_out.contiguous().view(batch_size, width, channels * height)
 | 
			
		||||
        
 | 
			
		||||
        # RNN序列建模
 | 
			
		||||
        rnn_out, _ = self.rnn(conv_out)
 | 
			
		||||
        
 | 
			
		||||
        # 全连接分类
 | 
			
		||||
        output = self.fc(rnn_out)
 | 
			
		||||
        
 | 
			
		||||
        # 转换为CTC需要的格式:(width, batch_size, num_classes)
 | 
			
		||||
        output = output.permute(1, 0, 2)
 | 
			
		||||
        
 | 
			
		||||
        return output
 | 
			
		||||
 | 
			
		||||
class CTCDecoder:
 | 
			
		||||
    """CTC解码器"""
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        # 定义中国车牌字符集(68个字符)
 | 
			
		||||
        self.chars = [
 | 
			
		||||
            # 空白字符(CTC需要)
 | 
			
		||||
            '<BLANK>',
 | 
			
		||||
            # 中文省份简称
 | 
			
		||||
            '京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑',
 | 
			
		||||
            '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤',
 | 
			
		||||
            '桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新',
 | 
			
		||||
            # 字母 A-Z
 | 
			
		||||
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
 | 
			
		||||
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 | 
			
		||||
            # 数字 0-9
 | 
			
		||||
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
 | 
			
		||||
        ]
 | 
			
		||||
        
 | 
			
		||||
        self.char_to_idx = {char: idx for idx, char in enumerate(self.chars)}
 | 
			
		||||
        self.idx_to_char = {idx: char for idx, char in enumerate(self.chars)}
 | 
			
		||||
        self.blank_idx = 0
 | 
			
		||||
    
 | 
			
		||||
    def decode_greedy(self, predictions):
 | 
			
		||||
        """贪婪解码"""
 | 
			
		||||
        # 获取每个时间步的最大概率索引
 | 
			
		||||
        indices = torch.argmax(predictions, dim=1)
 | 
			
		||||
        
 | 
			
		||||
        # CTC解码:移除重复字符和空白字符
 | 
			
		||||
        decoded_chars = []
 | 
			
		||||
        prev_idx = -1
 | 
			
		||||
        
 | 
			
		||||
        for idx in indices:
 | 
			
		||||
            idx = idx.item()
 | 
			
		||||
            if idx != prev_idx and idx != self.blank_idx:
 | 
			
		||||
                if idx < len(self.chars):
 | 
			
		||||
                    decoded_chars.append(self.chars[idx])
 | 
			
		||||
            prev_idx = idx
 | 
			
		||||
        
 | 
			
		||||
        return ''.join(decoded_chars)
 | 
			
		||||
    
 | 
			
		||||
    def decode_with_confidence(self, predictions):
 | 
			
		||||
        """解码并返回置信度信息"""
 | 
			
		||||
        # 应用softmax获得概率
 | 
			
		||||
        probs = torch.softmax(predictions, dim=1)
 | 
			
		||||
        
 | 
			
		||||
        # 贪婪解码
 | 
			
		||||
        indices = torch.argmax(probs, dim=1)
 | 
			
		||||
        max_probs = torch.max(probs, dim=1)[0]
 | 
			
		||||
        
 | 
			
		||||
        # CTC解码
 | 
			
		||||
        decoded_chars = []
 | 
			
		||||
        char_confidences = []
 | 
			
		||||
        prev_idx = -1
 | 
			
		||||
        
 | 
			
		||||
        for i, idx in enumerate(indices):
 | 
			
		||||
            idx = idx.item()
 | 
			
		||||
            confidence = max_probs[i].item()
 | 
			
		||||
            
 | 
			
		||||
            if idx != prev_idx and idx != self.blank_idx:
 | 
			
		||||
                if idx < len(self.chars):
 | 
			
		||||
                    decoded_chars.append(self.chars[idx])
 | 
			
		||||
                    char_confidences.append(confidence)
 | 
			
		||||
            prev_idx = idx
 | 
			
		||||
        
 | 
			
		||||
        text = ''.join(decoded_chars)
 | 
			
		||||
        avg_confidence = np.mean(char_confidences) if char_confidences else 0.0
 | 
			
		||||
        
 | 
			
		||||
        return text, avg_confidence, char_confidences
 | 
			
		||||
 | 
			
		||||
class LicensePlatePreprocessor:
 | 
			
		||||
    """车牌图像预处理器"""
 | 
			
		||||
    def __init__(self, target_height=32, target_width=128):
 | 
			
		||||
        self.target_height = target_height
 | 
			
		||||
        self.target_width = target_width
 | 
			
		||||
        
 | 
			
		||||
        # 定义图像变换
 | 
			
		||||
        self.transform = transforms.Compose([
 | 
			
		||||
            transforms.Resize((target_height, target_width)),
 | 
			
		||||
            transforms.ToTensor(),
 | 
			
		||||
            transforms.Normalize(mean=[0.485, 0.456, 0.406], 
 | 
			
		||||
                               std=[0.229, 0.224, 0.225])
 | 
			
		||||
        ])
 | 
			
		||||
    
 | 
			
		||||
    def preprocess_numpy_array(self, image_array):
 | 
			
		||||
        """预处理numpy数组格式的图像"""
 | 
			
		||||
        try:
 | 
			
		||||
            # 确保图像是RGB格式
 | 
			
		||||
            if len(image_array.shape) == 3 and image_array.shape[2] == 3:
 | 
			
		||||
                # 如果是BGR格式,转换为RGB
 | 
			
		||||
                if image_array.dtype == np.uint8:
 | 
			
		||||
                    image_array = cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB)
 | 
			
		||||
            
 | 
			
		||||
            # 转换为PIL图像
 | 
			
		||||
            if image_array.dtype != np.uint8:
 | 
			
		||||
                image_array = (image_array * 255).astype(np.uint8)
 | 
			
		||||
            
 | 
			
		||||
            image = Image.fromarray(image_array)
 | 
			
		||||
            
 | 
			
		||||
            # 应用变换
 | 
			
		||||
            tensor = self.transform(image)
 | 
			
		||||
            
 | 
			
		||||
            # 添加batch维度
 | 
			
		||||
            tensor = tensor.unsqueeze(0)
 | 
			
		||||
            
 | 
			
		||||
            return tensor
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"图像预处理失败: {e}")
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
def LPRNinitialize_model():
 | 
			
		||||
    """
 | 
			
		||||
    初始化CRNN模型
 | 
			
		||||
    
 | 
			
		||||
    返回:
 | 
			
		||||
        bool: 初始化是否成功
 | 
			
		||||
    """
 | 
			
		||||
    global crnn_model, crnn_decoder, crnn_preprocessor, device
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        # 设置设备
 | 
			
		||||
        device = 'cuda' if torch.cuda.is_available() else 'cpu'
 | 
			
		||||
        print(f"CRNN使用设备: {device}")
 | 
			
		||||
        
 | 
			
		||||
        # 初始化组件
 | 
			
		||||
        crnn_decoder = CTCDecoder()
 | 
			
		||||
        crnn_preprocessor = LicensePlatePreprocessor(target_height=32, target_width=128)
 | 
			
		||||
        
 | 
			
		||||
        # 创建模型实例
 | 
			
		||||
        crnn_model = CRNN(num_classes=len(crnn_decoder.chars), hidden_size=256)
 | 
			
		||||
        
 | 
			
		||||
        # 加载模型权重
 | 
			
		||||
        model_path = os.path.join(os.path.dirname(__file__), 'best_model.pth')
 | 
			
		||||
        
 | 
			
		||||
        if not os.path.exists(model_path):
 | 
			
		||||
            raise FileNotFoundError(f"模型文件不存在: {model_path}")
 | 
			
		||||
        
 | 
			
		||||
        print(f"正在加载CRNN模型: {model_path}")
 | 
			
		||||
        
 | 
			
		||||
        # 加载检查点
 | 
			
		||||
        checkpoint = torch.load(model_path, map_location=device, weights_only=False)
 | 
			
		||||
        
 | 
			
		||||
        # 处理不同的模型保存格式
 | 
			
		||||
        if isinstance(checkpoint, dict):
 | 
			
		||||
            if 'model_state_dict' in checkpoint:
 | 
			
		||||
                # 完整检查点格式
 | 
			
		||||
                state_dict = checkpoint['model_state_dict']
 | 
			
		||||
                print(f"检查点信息:")
 | 
			
		||||
                print(f"  - 训练轮次: {checkpoint.get('epoch', 'N/A')}")
 | 
			
		||||
                print(f"  - 最佳验证损失: {checkpoint.get('best_val_loss', 'N/A')}")
 | 
			
		||||
            else:
 | 
			
		||||
                # 精简模型格式(只包含权重)
 | 
			
		||||
                print("加载精简模型(仅权重)")
 | 
			
		||||
                state_dict = checkpoint
 | 
			
		||||
        else:
 | 
			
		||||
            # 直接是状态字典
 | 
			
		||||
            state_dict = checkpoint
 | 
			
		||||
        
 | 
			
		||||
        # 加载权重
 | 
			
		||||
        crnn_model.load_state_dict(state_dict)
 | 
			
		||||
        crnn_model.to(device)
 | 
			
		||||
        crnn_model.eval()
 | 
			
		||||
        
 | 
			
		||||
        print("CRNN模型初始化完成")
 | 
			
		||||
        
 | 
			
		||||
        # 统计模型参数
 | 
			
		||||
        total_params = sum(p.numel() for p in crnn_model.parameters())
 | 
			
		||||
        print(f"CRNN模型参数数量: {total_params:,}")
 | 
			
		||||
        
 | 
			
		||||
        return True
 | 
			
		||||
        
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"CRNN模型初始化失败: {e}")
 | 
			
		||||
        import traceback
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
def LPRNmodel_predict(image_array):
 | 
			
		||||
    """
 | 
			
		||||
    CRNN车牌号识别接口函数
 | 
			
		||||
    
 | 
			
		||||
    参数:
 | 
			
		||||
        image_array: numpy数组格式的车牌图像,已经过矫正处理
 | 
			
		||||
    
 | 
			
		||||
    返回:
 | 
			
		||||
        list: 包含最多8个字符的列表,代表车牌号的每个字符
 | 
			
		||||
              例如: ['京', 'A', '1', '2', '3', '4', '5', ''] (蓝牌7位+占位符)
 | 
			
		||||
                   ['京', 'A', 'D', '1', '2', '3', '4', '5'] (绿牌8位)
 | 
			
		||||
    """
 | 
			
		||||
    global crnn_model, crnn_decoder, crnn_preprocessor, device
 | 
			
		||||
    
 | 
			
		||||
    if crnn_model is None or crnn_decoder is None or crnn_preprocessor is None:
 | 
			
		||||
        print("CRNN模型未初始化,请先调用initialize_crnn_model()")
 | 
			
		||||
        return ['待', '识', '别', '0', '0', '0', '0', '0']
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        # 预处理图像
 | 
			
		||||
        input_tensor = crnn_preprocessor.preprocess_numpy_array(image_array)
 | 
			
		||||
        if input_tensor is None:
 | 
			
		||||
            raise ValueError("图像预处理失败")
 | 
			
		||||
        
 | 
			
		||||
        input_tensor = input_tensor.to(device)
 | 
			
		||||
        
 | 
			
		||||
        # 模型推理
 | 
			
		||||
        with torch.no_grad():
 | 
			
		||||
            outputs = crnn_model(input_tensor)  # (seq_len, batch_size, num_classes)
 | 
			
		||||
            
 | 
			
		||||
            # 移除batch维度
 | 
			
		||||
            outputs = outputs.squeeze(1)  # (seq_len, num_classes)
 | 
			
		||||
            
 | 
			
		||||
            # CTC解码
 | 
			
		||||
            predicted_text, confidence, char_confidences = crnn_decoder.decode_with_confidence(outputs)
 | 
			
		||||
            
 | 
			
		||||
            print(f"CRNN识别结果: {predicted_text}, 置信度: {confidence:.3f}")
 | 
			
		||||
            
 | 
			
		||||
            # 将字符串转换为字符列表
 | 
			
		||||
            char_list = list(predicted_text)
 | 
			
		||||
            
 | 
			
		||||
            # 确保返回至少7个字符,最多8个字符
 | 
			
		||||
            if len(char_list) < 7:
 | 
			
		||||
                # 如果识别结果少于7个字符,用'0'补齐到7位
 | 
			
		||||
                char_list.extend(['0'] * (7 - len(char_list)))
 | 
			
		||||
            elif len(char_list) > 8:
 | 
			
		||||
                # 如果识别结果多于8个字符,截取前8个
 | 
			
		||||
                char_list = char_list[:8]
 | 
			
		||||
            
 | 
			
		||||
            # 如果是7位,补齐到8位以保持接口一致性(第8位用空字符或占位符)
 | 
			
		||||
            if len(char_list) == 7:
 | 
			
		||||
                char_list.append('')  # 添加空字符作为第8位占位符
 | 
			
		||||
            
 | 
			
		||||
            return char_list
 | 
			
		||||
            
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"CRNN识别失败: {e}")
 | 
			
		||||
        import traceback
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
        return ['识', '别', '失', '败', '0', '0', '0', '0']
 | 
			
		||||
@@ -1,328 +0,0 @@
 | 
			
		||||
import torch
 | 
			
		||||
import torch.nn as nn
 | 
			
		||||
import cv2
 | 
			
		||||
import numpy as np
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
from torch.autograd import Variable
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
# 添加父目录到路径,以便导入模型和数据加载器
 | 
			
		||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
			
		||||
 | 
			
		||||
# LPRNet字符集定义(与训练时保持一致)
 | 
			
		||||
CHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑',
 | 
			
		||||
         '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤',
 | 
			
		||||
         '桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新',
 | 
			
		||||
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 | 
			
		||||
         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
 | 
			
		||||
         'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
 | 
			
		||||
         'W', 'X', 'Y', 'Z', 'I', 'O', '-']
 | 
			
		||||
 | 
			
		||||
CHARS_DICT = {char: i for i, char in enumerate(CHARS)}
 | 
			
		||||
 | 
			
		||||
# 简化的LPRNet模型定义
 | 
			
		||||
class small_basic_block(nn.Module):
 | 
			
		||||
    def __init__(self, ch_in, ch_out):
 | 
			
		||||
        super(small_basic_block, self).__init__()
 | 
			
		||||
        self.block = nn.Sequential(
 | 
			
		||||
            nn.Conv2d(ch_in, ch_out // 4, kernel_size=1),
 | 
			
		||||
            nn.ReLU(),
 | 
			
		||||
            nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(3, 1), padding=(1, 0)),
 | 
			
		||||
            nn.ReLU(),
 | 
			
		||||
            nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(1, 3), padding=(0, 1)),
 | 
			
		||||
            nn.ReLU(),
 | 
			
		||||
            nn.Conv2d(ch_out // 4, ch_out, kernel_size=1),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def forward(self, x):
 | 
			
		||||
        return self.block(x)
 | 
			
		||||
 | 
			
		||||
class LPRNet(nn.Module):
 | 
			
		||||
    def __init__(self, lpr_max_len, phase, class_num, dropout_rate):
 | 
			
		||||
        super(LPRNet, self).__init__()
 | 
			
		||||
        self.phase = phase
 | 
			
		||||
        self.lpr_max_len = lpr_max_len
 | 
			
		||||
        self.class_num = class_num
 | 
			
		||||
        self.backbone = nn.Sequential(
 | 
			
		||||
            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1), # 0
 | 
			
		||||
            nn.BatchNorm2d(num_features=64),
 | 
			
		||||
            nn.ReLU(),  # 2
 | 
			
		||||
            nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1)),
 | 
			
		||||
            small_basic_block(ch_in=64, ch_out=128),    # *** 4 ***
 | 
			
		||||
            nn.BatchNorm2d(num_features=128),
 | 
			
		||||
            nn.ReLU(),  # 6
 | 
			
		||||
            nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(2, 1, 2)),
 | 
			
		||||
            small_basic_block(ch_in=64, ch_out=256),   # 8
 | 
			
		||||
            nn.BatchNorm2d(num_features=256),
 | 
			
		||||
            nn.ReLU(),  # 10
 | 
			
		||||
            small_basic_block(ch_in=256, ch_out=256),   # *** 11 ***
 | 
			
		||||
            nn.BatchNorm2d(num_features=256),
 | 
			
		||||
            nn.ReLU(),  # 13
 | 
			
		||||
            nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2)),  # 14
 | 
			
		||||
            nn.Dropout(dropout_rate),
 | 
			
		||||
            nn.Conv2d(in_channels=64, out_channels=256, kernel_size=(1, 4), stride=1), # 16
 | 
			
		||||
            nn.BatchNorm2d(num_features=256),
 | 
			
		||||
            nn.ReLU(),  # 18
 | 
			
		||||
            nn.Dropout(dropout_rate),
 | 
			
		||||
            nn.Conv2d(in_channels=256, out_channels=class_num, kernel_size=(13, 1), stride=1), # 20
 | 
			
		||||
            nn.BatchNorm2d(num_features=class_num),
 | 
			
		||||
            nn.ReLU(),  # 22
 | 
			
		||||
        )
 | 
			
		||||
        self.container = nn.Sequential(
 | 
			
		||||
            nn.Conv2d(in_channels=448+self.class_num, out_channels=self.class_num, kernel_size=(1,1), stride=(1,1)),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def forward(self, x):
 | 
			
		||||
        keep_features = list()
 | 
			
		||||
        for i, layer in enumerate(self.backbone.children()):
 | 
			
		||||
            x = layer(x)
 | 
			
		||||
            if i in [2, 6, 13, 22]: # [2, 4, 8, 11, 22]
 | 
			
		||||
                keep_features.append(x)
 | 
			
		||||
 | 
			
		||||
        global_context = list()
 | 
			
		||||
        for i, f in enumerate(keep_features):
 | 
			
		||||
            if i in [0, 1]:
 | 
			
		||||
                f = nn.AvgPool2d(kernel_size=5, stride=5)(f)
 | 
			
		||||
            if i in [2]:
 | 
			
		||||
                f = nn.AvgPool2d(kernel_size=(4, 10), stride=(4, 2))(f)
 | 
			
		||||
            f_pow = torch.pow(f, 2)
 | 
			
		||||
            f_mean = torch.mean(f_pow)
 | 
			
		||||
            f = torch.div(f, f_mean)
 | 
			
		||||
            global_context.append(f)
 | 
			
		||||
 | 
			
		||||
        x = torch.cat(global_context, 1)
 | 
			
		||||
        x = self.container(x)
 | 
			
		||||
        logits = torch.mean(x, dim=2)
 | 
			
		||||
 | 
			
		||||
        return logits
 | 
			
		||||
 | 
			
		||||
class LPRNetInference:
 | 
			
		||||
    def __init__(self, model_path=None, img_size=[94, 24], lpr_max_len=8, dropout_rate=0.5):
 | 
			
		||||
        """
 | 
			
		||||
        初始化LPRNet推理类
 | 
			
		||||
        Args:
 | 
			
		||||
            model_path: 训练好的模型权重文件路径
 | 
			
		||||
            img_size: 输入图像尺寸 [width, height]
 | 
			
		||||
            lpr_max_len: 车牌最大长度
 | 
			
		||||
            dropout_rate: dropout率
 | 
			
		||||
        """
 | 
			
		||||
        self.img_size = img_size
 | 
			
		||||
        self.lpr_max_len = lpr_max_len
 | 
			
		||||
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 | 
			
		||||
        
 | 
			
		||||
        # 设置默认模型路径
 | 
			
		||||
        if model_path is None:
 | 
			
		||||
            current_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
            model_path = os.path.join(current_dir, 'LPRNet__iteration_74000.pth')
 | 
			
		||||
        
 | 
			
		||||
        # 初始化模型
 | 
			
		||||
        self.model = LPRNet(lpr_max_len=lpr_max_len, phase=False, class_num=len(CHARS), dropout_rate=dropout_rate)
 | 
			
		||||
        
 | 
			
		||||
        # 加载模型权重
 | 
			
		||||
        if model_path and os.path.exists(model_path):
 | 
			
		||||
            print(f"Loading LPRNet model from {model_path}")
 | 
			
		||||
            try:
 | 
			
		||||
                self.model.load_state_dict(torch.load(model_path, map_location=self.device))
 | 
			
		||||
                print("LPRNet模型权重加载成功")
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(f"Warning: 加载模型权重失败: {e}. 使用随机权重.")
 | 
			
		||||
        else:
 | 
			
		||||
            print(f"Warning: 模型文件不存在或未指定: {model_path}. 使用随机权重.")
 | 
			
		||||
        
 | 
			
		||||
        self.model.to(self.device)
 | 
			
		||||
        self.model.eval()
 | 
			
		||||
        
 | 
			
		||||
        print(f"LPRNet模型加载完成,设备: {self.device}")
 | 
			
		||||
        print(f"模型参数数量: {sum(p.numel() for p in self.model.parameters()):,}")
 | 
			
		||||
    
 | 
			
		||||
    def preprocess_image(self, image_array):
 | 
			
		||||
        """
 | 
			
		||||
        预处理图像数组 - 使用与训练时相同的预处理方式
 | 
			
		||||
        Args:
 | 
			
		||||
            image_array: numpy数组格式的图像 (H, W, C)
 | 
			
		||||
        Returns:
 | 
			
		||||
            preprocessed_image: 预处理后的图像tensor
 | 
			
		||||
        """
 | 
			
		||||
        if image_array is None:
 | 
			
		||||
            raise ValueError("Input image is None")
 | 
			
		||||
        
 | 
			
		||||
        # 确保图像是numpy数组
 | 
			
		||||
        if not isinstance(image_array, np.ndarray):
 | 
			
		||||
            raise ValueError("Input must be numpy array")
 | 
			
		||||
        
 | 
			
		||||
        # 检查图像维度
 | 
			
		||||
        if len(image_array.shape) != 3:
 | 
			
		||||
            raise ValueError(f"Expected 3D image array, got {len(image_array.shape)}D")
 | 
			
		||||
        
 | 
			
		||||
        height, width, channels = image_array.shape
 | 
			
		||||
        if channels != 3:
 | 
			
		||||
            raise ValueError(f"Expected 3 channels, got {channels}")
 | 
			
		||||
        
 | 
			
		||||
        # 调整图像尺寸到模型要求的尺寸
 | 
			
		||||
        if height != self.img_size[1] or width != self.img_size[0]:
 | 
			
		||||
            image_array = cv2.resize(image_array, tuple(self.img_size))
 | 
			
		||||
        
 | 
			
		||||
        # 使用与训练时相同的预处理方式
 | 
			
		||||
        image_array = image_array.astype('float32')
 | 
			
		||||
        image_array -= 127.5
 | 
			
		||||
        image_array *= 0.0078125
 | 
			
		||||
        image_array = np.transpose(image_array, (2, 0, 1))  # HWC -> CHW
 | 
			
		||||
        
 | 
			
		||||
        # 转换为tensor并添加batch维度
 | 
			
		||||
        image_tensor = torch.from_numpy(image_array).unsqueeze(0)
 | 
			
		||||
        
 | 
			
		||||
        return image_tensor
 | 
			
		||||
    
 | 
			
		||||
    def decode_prediction(self, logits):
 | 
			
		||||
        """
 | 
			
		||||
        解码模型预测结果 - 使用正确的CTC贪婪解码
 | 
			
		||||
        Args:
 | 
			
		||||
            logits: 模型输出的logits [batch_size, num_classes, sequence_length]
 | 
			
		||||
        Returns:
 | 
			
		||||
            predicted_text: 预测的车牌号码
 | 
			
		||||
        """
 | 
			
		||||
        # 转换为numpy进行处理
 | 
			
		||||
        prebs = logits.cpu().detach().numpy()
 | 
			
		||||
        preb = prebs[0, :, :]  # 取第一个batch [num_classes, sequence_length]
 | 
			
		||||
        
 | 
			
		||||
        # 贪婪解码:对每个时间步选择最大概率的字符
 | 
			
		||||
        preb_label = []
 | 
			
		||||
        for j in range(preb.shape[1]):  # 遍历每个时间步
 | 
			
		||||
            preb_label.append(np.argmax(preb[:, j], axis=0))
 | 
			
		||||
        
 | 
			
		||||
        # CTC解码:去除重复字符和空白字符
 | 
			
		||||
        no_repeat_blank_label = []
 | 
			
		||||
        pre_c = preb_label[0]
 | 
			
		||||
        
 | 
			
		||||
        # 处理第一个字符
 | 
			
		||||
        if pre_c != len(CHARS) - 1:  # 不是空白字符
 | 
			
		||||
            no_repeat_blank_label.append(pre_c)
 | 
			
		||||
        
 | 
			
		||||
        # 处理后续字符
 | 
			
		||||
        for c in preb_label:
 | 
			
		||||
            if (pre_c == c) or (c == len(CHARS) - 1):  # 重复字符或空白字符
 | 
			
		||||
                if c == len(CHARS) - 1:
 | 
			
		||||
                    pre_c = c
 | 
			
		||||
                continue
 | 
			
		||||
            no_repeat_blank_label.append(c)
 | 
			
		||||
            pre_c = c
 | 
			
		||||
        
 | 
			
		||||
        # 转换为字符
 | 
			
		||||
        decoded_chars = [CHARS[idx] for idx in no_repeat_blank_label]
 | 
			
		||||
        return ''.join(decoded_chars)
 | 
			
		||||
    
 | 
			
		||||
    def predict(self, image_array):
 | 
			
		||||
        """
 | 
			
		||||
        预测单张图像的车牌号码
 | 
			
		||||
        Args:
 | 
			
		||||
            image_array: numpy数组格式的图像
 | 
			
		||||
        Returns:
 | 
			
		||||
            prediction: 预测的车牌号码
 | 
			
		||||
            confidence: 预测置信度
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # 预处理图像
 | 
			
		||||
            image = self.preprocess_image(image_array)
 | 
			
		||||
            if image is None:
 | 
			
		||||
                return None, 0.0
 | 
			
		||||
            
 | 
			
		||||
            image = image.to(self.device)
 | 
			
		||||
            
 | 
			
		||||
            # 模型推理
 | 
			
		||||
            with torch.no_grad():
 | 
			
		||||
                logits = self.model(image)
 | 
			
		||||
                # logits shape: [batch_size, class_num, sequence_length]
 | 
			
		||||
                
 | 
			
		||||
                # 计算置信度(使用softmax后的最大概率平均值)
 | 
			
		||||
                probs = torch.softmax(logits, dim=1)
 | 
			
		||||
                max_probs = torch.max(probs, dim=1)[0]
 | 
			
		||||
                confidence = torch.mean(max_probs).item()
 | 
			
		||||
                
 | 
			
		||||
                # 解码预测结果
 | 
			
		||||
                prediction = self.decode_prediction(logits)
 | 
			
		||||
            
 | 
			
		||||
            return prediction, confidence
 | 
			
		||||
            
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"预测图像失败: {e}")
 | 
			
		||||
            return None, 0.0
 | 
			
		||||
 | 
			
		||||
# 全局变量
 | 
			
		||||
lpr_model = None
 | 
			
		||||
 | 
			
		||||
def LPRNinitialize_model():
 | 
			
		||||
    """
 | 
			
		||||
    初始化LPRNet模型
 | 
			
		||||
    
 | 
			
		||||
    返回:
 | 
			
		||||
        bool: 初始化是否成功
 | 
			
		||||
    """
 | 
			
		||||
    global lpr_model
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        # 模型权重文件路径
 | 
			
		||||
        model_path = os.path.join(os.path.dirname(__file__), 'LPRNet__iteration_74000.pth')
 | 
			
		||||
        
 | 
			
		||||
        # 创建推理对象
 | 
			
		||||
        lpr_model = LPRNetInference(model_path)
 | 
			
		||||
        
 | 
			
		||||
        print("LPRNet模型初始化完成")
 | 
			
		||||
        return True
 | 
			
		||||
        
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"LPRNet模型初始化失败: {e}")
 | 
			
		||||
        import traceback
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
def LPRNmodel_predict(image_array):
 | 
			
		||||
    """
 | 
			
		||||
    LPRNet车牌号识别接口函数
 | 
			
		||||
    
 | 
			
		||||
    参数:
 | 
			
		||||
        image_array: numpy数组格式的车牌图像,已经过矫正处理
 | 
			
		||||
    
 | 
			
		||||
    返回:
 | 
			
		||||
        list: 包含最多8个字符的列表,代表车牌号的每个字符
 | 
			
		||||
              例如: ['京', 'A', '1', '2', '3', '4', '5'] (蓝牌7位)
 | 
			
		||||
                   ['京', 'A', 'D', '1', '2', '3', '4', '5'] (绿牌8位)
 | 
			
		||||
    """
 | 
			
		||||
    global lpr_model
 | 
			
		||||
    
 | 
			
		||||
    if lpr_model is None:
 | 
			
		||||
        print("LPRNet模型未初始化,请先调用LPRNinitialize_model()")
 | 
			
		||||
        return ['待', '识', '别', '0', '0', '0', '0', '0']
 | 
			
		||||
    
 | 
			
		||||
    try:
 | 
			
		||||
        # 预测车牌号
 | 
			
		||||
        predicted_text, confidence = lpr_model.predict(image_array)
 | 
			
		||||
        
 | 
			
		||||
        if predicted_text is None:
 | 
			
		||||
            print("LPRNet识别失败")
 | 
			
		||||
            return ['识', '别', '失', '败', '0', '0', '0', '0']
 | 
			
		||||
        
 | 
			
		||||
        print(f"LPRNet识别结果: {predicted_text}, 置信度: {confidence:.3f}")
 | 
			
		||||
        
 | 
			
		||||
        # 将字符串转换为字符列表
 | 
			
		||||
        char_list = list(predicted_text)
 | 
			
		||||
        
 | 
			
		||||
        # 确保返回至少7个字符,最多8个字符
 | 
			
		||||
        if len(char_list) < 7:
 | 
			
		||||
            # 如果识别结果少于7个字符,用'0'补齐到7位
 | 
			
		||||
            char_list.extend(['0'] * (7 - len(char_list)))
 | 
			
		||||
        elif len(char_list) > 8:
 | 
			
		||||
            # 如果识别结果多于8个字符,截取前8个
 | 
			
		||||
            char_list = char_list[:8]
 | 
			
		||||
        
 | 
			
		||||
        # 如果是7位,补齐到8位以保持接口一致性(第8位用空字符或占位符)
 | 
			
		||||
        if len(char_list) == 7:
 | 
			
		||||
            char_list.append('')  # 添加空字符作为第8位占位符
 | 
			
		||||
        
 | 
			
		||||
        return char_list
 | 
			
		||||
        
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"LPRNet识别失败: {e}")
 | 
			
		||||
        import traceback
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
        return ['识', '别', '失', '败', '0', '0', '0', '0']
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
import numpy as np
 | 
			
		||||
from paddleocr import TextRecognition
 | 
			
		||||
import cv2
 | 
			
		||||
 | 
			
		||||
class OCRProcessor:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.model = TextRecognition(model_name="PP-OCRv5_server_rec")
 | 
			
		||||
        print("OCR模型初始化完成(占位)")
 | 
			
		||||
 | 
			
		||||
    def predict(self, image_array):
 | 
			
		||||
        # 保持原有模型调用方式
 | 
			
		||||
        output = self.model.predict(input=image_array)
 | 
			
		||||
        # 结构化输出结果
 | 
			
		||||
        results = output[0]["rec_text"]
 | 
			
		||||
        placeholder_result = results.split(',')
 | 
			
		||||
        return placeholder_result
 | 
			
		||||
 | 
			
		||||
# 保留原有函数接口
 | 
			
		||||
_processor = OCRProcessor()
 | 
			
		||||
 | 
			
		||||
def LPRNinitialize_model():
 | 
			
		||||
    return _processor
 | 
			
		||||
 | 
			
		||||
def LPRNmodel_predict(image_array):
 | 
			
		||||
    """
 | 
			
		||||
    OCR车牌号识别接口函数
 | 
			
		||||
    
 | 
			
		||||
    参数:
 | 
			
		||||
        image_array: numpy数组格式的车牌图像,已经过矫正处理
 | 
			
		||||
    
 | 
			
		||||
    返回:
 | 
			
		||||
        list: 包含最多8个字符的列表,代表车牌号的每个字符
 | 
			
		||||
              例如: ['京', 'A', '1', '2', '3', '4', '5', ''] (蓝牌7位+占位符)
 | 
			
		||||
                   ['京', 'A', 'D', '1', '2', '3', '4', '5'] (绿牌8位)
 | 
			
		||||
    """
 | 
			
		||||
    # 获取原始预测结果
 | 
			
		||||
    raw_result = _processor.predict(image_array)
 | 
			
		||||
    
 | 
			
		||||
    # 将结果合并为字符串(如果是列表的话)
 | 
			
		||||
    if isinstance(raw_result, list):
 | 
			
		||||
        result_str = ''.join(raw_result)
 | 
			
		||||
    else:
 | 
			
		||||
        result_str = str(raw_result)
 | 
			
		||||
    
 | 
			
		||||
    # 过滤掉'·'字符
 | 
			
		||||
    filtered_str = result_str.replace('·', '')
 | 
			
		||||
    
 | 
			
		||||
    # 转换为字符列表
 | 
			
		||||
    char_list = list(filtered_str)
 | 
			
		||||
    
 | 
			
		||||
    # 确保返回至少7个字符,最多8个字符
 | 
			
		||||
    if len(char_list) < 7:
 | 
			
		||||
        # 如果识别结果少于7个字符,用'0'补齐到7位
 | 
			
		||||
        char_list.extend(['0'] * (7 - len(char_list)))
 | 
			
		||||
    elif len(char_list) > 8:
 | 
			
		||||
        # 如果识别结果多于8个字符,截取前8个
 | 
			
		||||
        char_list = char_list[:8]
 | 
			
		||||
    
 | 
			
		||||
    # 如果是7位,补齐到8位以保持接口一致性(第8位用空字符或占位符)
 | 
			
		||||
    if len(char_list) == 7:
 | 
			
		||||
        char_list.append('')  # 添加空字符作为第8位占位符
 | 
			
		||||
    
 | 
			
		||||
    return char_list
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										172
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								README.md
									
									
									
									
									
								
							@@ -1,172 +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接口(占位)
 | 
			
		||||
└── LPRNET_part/              # LPRNet识别模块
 | 
			
		||||
    ├── lpr_interface.py      # LPRNet接口(已完成)
 | 
			
		||||
    └── LPRNet__iteration_74000.pth  # LPRNet模型权重文件
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 功能特性
 | 
			
		||||
 | 
			
		||||
### 1. 实时车牌检测
 | 
			
		||||
- 基于YOLO11 Pose模型进行车牌检测
 | 
			
		||||
- 支持蓝牌(类别0)和绿牌(类别1)识别
 | 
			
		||||
- 实时摄像头画面处理
 | 
			
		||||
 | 
			
		||||
### 2. 四角点定位
 | 
			
		||||
- 检测车牌的四个角点:right_bottom, left_bottom, left_top, right_top
 | 
			
		||||
- 只有检测到完整四个角点的车牌才进行后续处理
 | 
			
		||||
- 用黄色线条连接四个角点显示检测结果
 | 
			
		||||
 | 
			
		||||
### 3. 透视矫正
 | 
			
		||||
- 使用四个角点进行透视变换
 | 
			
		||||
- 将倾斜的车牌矫正为标准矩形
 | 
			
		||||
- 输出标准尺寸的车牌图像供识别使用
 | 
			
		||||
 | 
			
		||||
### 4. 多种识别方案
 | 
			
		||||
- 支持OCR、CRNN和LPRNet三种车牌识别方法
 | 
			
		||||
- LPRNet模型准确率高达98%
 | 
			
		||||
- 模块化接口设计,便于切换不同识别算法
 | 
			
		||||
 | 
			
		||||
### 5. PyQt界面
 | 
			
		||||
- 左侧:实时摄像头画面显示
 | 
			
		||||
- 右侧:检测结果展示区域
 | 
			
		||||
  - 顶部显示识别到的车牌数量
 | 
			
		||||
  - 每行显示:车牌类型、矫正后图像、车牌号
 | 
			
		||||
- 美观的现代化界面设计
 | 
			
		||||
 | 
			
		||||
### 6. 模块化设计
 | 
			
		||||
- yolopart:负责车牌定位和矫正
 | 
			
		||||
- OCR_part/CRNN_part/LPRNET_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. 选择识别模块
 | 
			
		||||
在 `main.py` 中修改导入语句来选择不同的识别方案:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
# 使用LPRNet(推荐,准确率98%)
 | 
			
		||||
from LPRNET_part.lpr_interface import LPRNmodel_predict, LPRNinitialize_model
 | 
			
		||||
 | 
			
		||||
# 使用OCR
 | 
			
		||||
from OCR_part.ocr_interface import LPRNmodel_predict, LPRNinitialize_model
 | 
			
		||||
 | 
			
		||||
# 使用CRNN
 | 
			
		||||
from CRNN_part.crnn_interface import LPRNmodel_predict, LPRNinitialize_model
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 6. 使用说明
 | 
			
		||||
1. 点击"启动摄像头"按钮开始检测
 | 
			
		||||
2. 将车牌对准摄像头
 | 
			
		||||
3. 系统会自动检测车牌并显示:
 | 
			
		||||
   - 检测框和角点连线
 | 
			
		||||
   - 右侧显示车牌类型、矫正图像和车牌号
 | 
			
		||||
4. 点击"停止摄像头"结束检测
 | 
			
		||||
 | 
			
		||||
## 模型输出格式
 | 
			
		||||
 | 
			
		||||
YOLO Pose模型输出包含:
 | 
			
		||||
- **检测框**:车牌的边界框坐标
 | 
			
		||||
- **类别**:0=蓝牌,1=绿牌
 | 
			
		||||
- **置信度**:检测置信度分数
 | 
			
		||||
- **关键点**:四个角点坐标
 | 
			
		||||
  - right_bottom:右下角
 | 
			
		||||
  - left_bottom:左下角  
 | 
			
		||||
  - left_top:左上角
 | 
			
		||||
  - right_top:右上角
 | 
			
		||||
 | 
			
		||||
## 接口说明
 | 
			
		||||
 | 
			
		||||
### 车牌识别接口
 | 
			
		||||
 | 
			
		||||
项目为OCR、CRNN和LPRNet识别模块提供了标准接口:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
# 接口函数名(导入所需模块,每个模块统一函数名)
 | 
			
		||||
 | 
			
		||||
# 初始化
 | 
			
		||||
from 对应模块 import LPRNinitialize_model
 | 
			
		||||
LPRNinitialize_model()
 | 
			
		||||
 | 
			
		||||
# 预测主函数
 | 
			
		||||
from 对应模块 import LPRNmodel_predict
 | 
			
		||||
result = LPRNmodel_predict(corrected_image)  # 返回7个字符的列表
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 输入参数
 | 
			
		||||
- `corrected_image`:numpy数组格式的矫正后车牌图像
 | 
			
		||||
 | 
			
		||||
### 返回值
 | 
			
		||||
- 长度为7的字符列表,包含车牌号的每个字符
 | 
			
		||||
- 例如:`['京', 'A', '1', '2', '3', '4', '5']`
 | 
			
		||||
 | 
			
		||||
### LPRNet模块特性
 | 
			
		||||
 | 
			
		||||
- **高准确率**: 模型准确率高达98%
 | 
			
		||||
- **快速推理**: 基于深度学习的端到端识别
 | 
			
		||||
- **CTC解码**: 使用CTC(Connectionist Temporal Classification)解码算法
 | 
			
		||||
- **支持中文**: 完整支持中文省份简称和字母数字组合
 | 
			
		||||
- **模型权重**: 使用预训练的LPRNet__iteration_74000.pth权重文件
 | 
			
		||||
 | 
			
		||||
## 开发说明
 | 
			
		||||
 | 
			
		||||
### 添加新的识别算法
 | 
			
		||||
1. 在对应目录(OCR_part或CRNN_part)实现识别函数
 | 
			
		||||
2. 确保函数签名与接口一致
 | 
			
		||||
3. 在main.py中导入对应模块即可
 | 
			
		||||
 | 
			
		||||
### 自定义模型
 | 
			
		||||
1. 替换 `yolopart/yolo11s-pose42.pt` 文件
 | 
			
		||||
2. 确保新模型输出格式与现有接口兼容
 | 
			
		||||
3. 根据需要调整类别名称和数量
 | 
			
		||||
 | 
			
		||||
### 调试模式
 | 
			
		||||
在代码中设置调试标志可以输出更多信息:
 | 
			
		||||
```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
 | 
			
		||||
							
								
								
									
										436
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										436
									
								
								main.py
									
									
									
									
									
								
							@@ -1,436 +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 LPRNET_part.lpr_interface import LPRNmodel_predict
 | 
			
		||||
from LPRNET_part.lpr_interface import LPRNinitialize_model
 | 
			
		||||
 | 
			
		||||
#使用OCR
 | 
			
		||||
#from OCR_part.ocr_interface import LPRNmodel_predict
 | 
			
		||||
#from OCR_part.ocr_interface import LPRNinitialize_model
 | 
			
		||||
# 使用CRNN
 | 
			
		||||
#from CRNN_part.crnn_interface import LPRNmodel_predict
 | 
			
		||||
#from CRNN_part.crnn_interface import LPRNinitialize_model
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
 | 
			
		||||
        # 初始化OCR/CRNN模型(函数名改成一样的了,所以不要修改这里了,想用哪个模块直接导入)
 | 
			
		||||
        LPRNinitialize_model()
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    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, detection['class_name'])
 | 
			
		||||
            
 | 
			
		||||
            # 创建车牌显示组件
 | 
			
		||||
            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, class_name):
 | 
			
		||||
        """识别车牌号"""
 | 
			
		||||
        if corrected_image is None:
 | 
			
		||||
            return "识别失败"
 | 
			
		||||
        
 | 
			
		||||
        try:
 | 
			
		||||
            # 预测函数(来自模块)
 | 
			
		||||
            # 函数名改成一样的了,所以不要修改这里了,想用哪个模块直接导入
 | 
			
		||||
            result = LPRNmodel_predict(corrected_image)
 | 
			
		||||
            
 | 
			
		||||
            # 将字符列表转换为字符串,支持8位车牌号
 | 
			
		||||
            if isinstance(result, list) and len(result) >= 7:
 | 
			
		||||
                # 根据车牌类型决定显示位数
 | 
			
		||||
                if class_name == '绿牌' and len(result) >= 8:
 | 
			
		||||
                    # 绿牌显示8位,过滤掉空字符占位符
 | 
			
		||||
                    plate_chars = [char for char in result[:8] if char != '']
 | 
			
		||||
                    # 如果过滤后确实有8位,显示8位;否则显示7位
 | 
			
		||||
                    if len(plate_chars) == 8:
 | 
			
		||||
                        return ''.join(plate_chars)
 | 
			
		||||
                    else:
 | 
			
		||||
                        return ''.join(plate_chars[:7])
 | 
			
		||||
                else:
 | 
			
		||||
                    # 蓝牌或其他类型显示前7位,过滤掉空字符
 | 
			
		||||
                    plate_chars = [char for char in result[:7] if char != '']
 | 
			
		||||
                    return ''.join(plate_chars)
 | 
			
		||||
            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,25 +0,0 @@
 | 
			
		||||
# 车牌识别系统依赖包
 | 
			
		||||
 | 
			
		||||
# 深度学习和计算机视觉
 | 
			
		||||
ultralytics>=8.0.0
 | 
			
		||||
opencv-python>=4.5.0
 | 
			
		||||
numpy>=1.21.0
 | 
			
		||||
 | 
			
		||||
# PyQt5界面
 | 
			
		||||
PyQt5>=5.15.0
 | 
			
		||||
 | 
			
		||||
# 图像处理
 | 
			
		||||
Pillow>=8.0.0
 | 
			
		||||
 | 
			
		||||
#paddleocr
 | 
			
		||||
python -m pip install paddlepaddle-gpu==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/
 | 
			
		||||
python -m pip install "paddleocr[all]"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 可选:如果需要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.
										
									
								
							
		Reference in New Issue
	
	Block a user