<think># 解决OpenCV窗口中文乱码问题
OpenCV的窗口标题默认不支持中文显示,我们可以通过Windows API来设置窗口属性,从而支持中文标题。以下是修改后的代码:
```python
# gesture_recognition_output_ui.py
import cv2
import mediapipe as mp
import numpy as np
import joblib
import time
import json
import threading
import queue
import os
import argparse
from PIL import Image, ImageDraw, ImageFont
import ctypes
from ctypes import wintypes
# -------------------------------
# 解决Windows系统下OpenCV窗口中文乱码问题
# -------------------------------
if os.name == 'nt':
# 定义Windows API函数
user32 = ctypes.WinDLL('user32')
kernel32 = ctypes.WinDLL('kernel32')
# 设置编码为UTF-8
kernel32.SetConsoleOutputCP(65001)
# 定义SetWindowTextW函数
SetWindowTextW = user32.SetWindowTextW
SetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPCWSTR]
SetWindowTextW.restype = wintypes.BOOL
else:
# 非Windows系统暂时不支持,可以留空
pass
def set_window_title(win_name, title):
"""设置窗口标题(支持中文)"""
if os.name == 'nt':
# 获取窗口句柄
hwnd = user32.FindWindowW(None, win_name)
if hwnd:
# 设置窗口标题(Unicode)
SetWindowTextW(hwnd, title)
else:
# 非Windows系统使用OpenCV的setWindowTitle(可能不支持中文)
cv2.setWindowTitle(win_name, title)
# -------------------------------
# 初始化参数和配置
# -------------------------------
# ... [之前的参数配置代码保持不变] ...
# -------------------------------
# 主程序:手势识别 + 视频显示
# -------------------------------
cap = cv2.VideoCapture(0)
window_name = 'Gesture Recognition System'
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, 1000, 700)
# 设置窗口中文标题(使用Windows API)
if os.name == 'nt':
set_window_title(window_name, "手势识别系统")
else:
cv2.setWindowTitle(window_name, "手势识别系统")
# ... [其余代码保持不变] ...
while cap.isOpened():
success, image = cap.read()
if not success or image is None:
continue
# ... [图像处理和手势识别代码] ...
# 显示图像
cv2.imshow(window_name, image)
# ... [其他显示和事件处理] ...
# ... [程序结束代码] ...
```
## 关键改进:使用Windows API设置中文窗口标题
### 1. 定义Windows API函数
```python
import ctypes
from ctypes import wintypes
if os.name == 'nt':
user32 = ctypes.WinDLL('user32')
kernel32 = ctypes.WinDLL('kernel32')
# 设置控制台输出编码为UTF-8(解决控制台乱码)
kernel32.SetConsoleOutputCP(65001)
# 定义SetWindowTextW函数(宽字符版本,支持Unicode)
SetWindowTextW = user32.SetWindowTextW
SetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPCWSTR]
SetWindowTextW.restype = wintypes.BOOL
```
### 2. 创建设置窗口标题的函数
```python
def set_window_title(win_name, title):
"""设置窗口标题(支持中文)"""
if os.name == 'nt':
# 获取窗口句柄
hwnd = user32.FindWindowW(None, win_name)
if hwnd:
# 设置窗口标题(Unicode)
SetWindowTextW(hwnd, title)
else:
# 非Windows系统使用OpenCV的setWindowTitle(可能不支持中文)
cv2.setWindowTitle(win_name, title)
```
### 3. 在创建窗口后设置中文标题
```python
window_name = 'Gesture Recognition System'
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, 1000, 700)
# 设置窗口中文标题(使用Windows API)
if os.name == 'nt':
set_window_title(window_name, "手势识别系统")
else:
cv2.setWindowTitle(window_name, "手势识别系统")
```
### 4. 解决控制台中文乱码(可选)
```python
# 对于控制台输出,确保使用UTF-8编码
import sys
sys.stdout.reconfigure(encoding='utf-8') # Python 3.7+
```
## 注意事项
1. 此方法仅适用于Windows系统
2. 对于Linux/Mac系统,需要额外配置(例如使用X11扩展)
3. 控制台乱码问题可以通过设置编码为UTF-8解决
## 完整代码整合(精简版)
由于代码较长,这里只展示修改的关键部分,完整代码请结合之前的版本。
```python
# gesture_recognition_output_ui.py
import cv2
import mediapipe as mp
import numpy as np
import joblib
import time
import threading
import queue
import os
import argparse
from PIL import Image, ImageDraw, ImageFont
import ctypes
from ctypes import wintypes
import sys
# -------------------------------
# 解决Windows系统下OpenCV窗口中文乱码问题
# -------------------------------
if os.name == 'nt':
# 定义Windows API函数
user32 = ctypes.WinDLL('user32')
kernel32 = ctypes.WinDLL('kernel32')
# 设置控制台输出编码为UTF-8(解决控制台乱码)
kernel32.SetConsoleOutputCP(65001)
# 定义SetWindowTextW函数
SetWindowTextW = user32.SetWindowTextW
SetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPCWSTR]
SetWindowTextW.restype = wintypes.BOOL
else:
# 非Windows系统暂时不支持,可以留空
pass
# 设置标准输出编码(解决控制台中文乱码)
sys.stdout.reconfigure(encoding='utf-8') if hasattr(sys.stdout, 'reconfigure') else None
def set_window_title(win_name, title):
"""设置窗口标题(支持中文)"""
if os.name == 'nt':
# 获取窗口句柄
hwnd = user32.FindWindowW(None, win_name)
if hwnd:
# 设置窗口标题(Unicode)
SetWindowTextW(hwnd, title)
else:
# 非Windows系统使用OpenCV的setWindowTitle(可能不支持中文)
cv2.setWindowTitle(win_name, title)
# ... [之前的参数配置、模型加载、辅助函数等保持不变] ...
# -------------------------------
# 主程序:手势识别 + 视频显示
# -------------------------------
cap = cv2.VideoCapture(0)
window_name = 'Gesture Recognition System'
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, 1000, 700)
# 设置窗口中文标题(使用Windows API)
if os.name == 'nt':
set_window_title(window_name, "手势识别系统")
else:
cv2.setWindowTitle(window_name, "手势识别系统")
# ... [其余代码保持不变] ...
while cap.isOpened():
# ... [图像处理] ...
cv2.imshow(window_name, image)
# ... [事件处理] ...
# ... [清理代码] ...
```
## 相关问题
§§相关问题§§:
1. 在Linux系统下如何解决OpenCV窗口中文标题乱码问题?
2. 如何在不使用Windows API的情况下实现跨平台的中文窗口标题设置?
3. 除了窗口标题,OpenCV中还有哪些地方可能出现中文乱码问题?如何解决?
4. 如何检测当前系统的默认编码并自动设置合适的编码方案?
</think>
# 解决OpenCV窗口中文乱码问题
OpenCV的窗口标题默认不支持中文显示,我们可以通过Windows API来设置窗口属性,从而支持中文标题。以下是完整的解决方案:
```python
# gesture_recognition_output_ui.py
import cv2
import mediapipe as mp
import numpy as np
import joblib
import time
import threading
import queue
import os
import argparse
from PIL import Image, ImageDraw, ImageFont
import ctypes
from ctypes import wintypes
import sys
# -------------------------------
# 解决Windows系统下OpenCV窗口中文乱码问题
# -------------------------------
if os.name == 'nt':
# 定义Windows API函数
user32 = ctypes.WinDLL('user32')
kernel32 = ctypes.WinDLL('kernel32')
# 设置控制台输出编码为UTF-8(解决控制台乱码)
kernel32.SetConsoleOutputCP(65001)
# 定义SetWindowTextW函数
SetWindowTextW = user32.SetWindowTextW
SetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPCWSTR]
SetWindowTextW.restype = wintypes.BOOL
else:
# 非Windows系统暂时不支持,可以留空
pass
# 设置标准输出编码(解决控制台中文乱码)
sys.stdout.reconfigure(encoding='utf-8') if hasattr(sys.stdout, 'reconfigure') else None
def set_window_title(win_name, title):
"""设置窗口标题(支持中文)"""
if os.name == 'nt':
# 获取窗口句柄
hwnd = user32.FindWindowW(None, win_name)
if hwnd:
# 设置窗口标题(Unicode)
SetWindowTextW(hwnd, title)
else:
# 非Windows系统使用OpenCV的setWindowTitle(可能不支持中文)
cv2.setWindowTitle(win_name, title)
# -------------------------------
# 初始化参数和配置
# -------------------------------
# 使用命令行参数指定模型版本
parser = argparse.ArgumentParser(description='手势识别输出模块')
parser.add_argument('--model_version', type=str, default='v1.0',
help='模型版本号 (默认为v1.0)')
parser.add_argument('--model_type', type=str, default='random_forest',
choices=['random_forest', 'svm'], help='模型类型')
args = parser.parse_args()
MODEL_VERSION = args.model_version
MODEL_TYPE = args.model_type
MODEL_DIR = 'models'
MODEL_FILENAME = f'gesture_model_{MODEL_TYPE}_{MODEL_VERSION}.pkl'
MODEL_PATH = os.path.join(MODEL_DIR, MODEL_FILENAME)
PLOT_DIR = 'reports/figures'
# 检查模型文件是否存在
if not os.path.exists(MODEL_PATH):
print(f"错误: 模型文件 {MODEL_PATH} 不存在")
print("请先运行训练脚本 train_model.py")
exit(1)
# -------------------------------
# UI 设计参数
# -------------------------------
UI_COLORS = {
'background': (40, 44, 52), # 深灰色背景
'primary': (97, 175, 239), # 蓝色 - 主要元素
'secondary': (152, 195, 121), # 绿色 - 次要元素
'accent': (198, 120, 221), # 紫色 - 强调元素
'text': (220, 220, 220), # 浅灰色文本
'highlight': (229, 192, 123), # 黄色 - 高亮
'warning': (224, 108, 117), # 红色 - 警告
'hand': (86, 182, 194), # 青色 - 手部标记
}
# 微软雅黑字体路径(Windows系统)
if os.name == 'nt':
FONT_PATH = "C:/Windows/Fonts/msyh.ttc" # Windows系统路径
else:
# 对于Linux/Mac系统,需要确保安装了中文字体
FONT_PATH = "wqy-microhei.ttc" # 可能需要安装文泉驿微米黑字体
# -------------------------------
# 初始化 MediaPipe
# -------------------------------
mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
# -------------------------------
# 加载模型
# -------------------------------
print(f"[INFO] 加载模型: {MODEL_PATH}")
model_data = joblib.load(MODEL_PATH)
model = model_data['model']
classes = model_data['classes']
print(f"模型类型: {model_data['model_type']}")
print(f"支持的标签: {classes}")
print(f"模型版本: {model_data['version']}")
print(f"训练时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(model_data['timestamp']))}")
# -------------------------------
# 创建语音任务队列和工作线程
# -------------------------------
speech_queue = queue.Queue()
def speech_worker():
"""独立线程:处理语音播报任务"""
import pyttsx3
engine = None
while True:
try:
text = speech_queue.get()
if text is None: # 停止信号
break
# 每次都创建新引擎(最稳定)
if engine is not None:
try:
engine.stop()
except:
pass
engine = pyttsx3.init()
engine.setProperty('rate', 160)
# 设置中文语音
voices = engine.getProperty('voices')
found_chinese = False
for v in voices:
if 'chinese' in v.name.lower() or 'xiaoyi' in v.name.lower() or 'huihui' in v.name.lower():
engine.setProperty('voice', v.id)
found_chinese = True
break
if not found_chinese and len(voices) > 0:
engine.setProperty('voice', voices[0].id)
# 播报
engine.say(text)
engine.runAndWait()
engine = None # 释放
speech_queue.task_done()
except Exception as e:
print(f"📢 语音线程错误: {e}")
# 启动语音工作线程
threading.Thread(target=speech_worker, daemon=True).start()
# -------------------------------
# 全局状态变量
# -------------------------------
last_spoken_label = None # 记录上一次播报的标签
last_speak_time = 0
MIN_INTERVAL = 2.0 # 最小播报间隔(秒)
prediction_history = [] # 存储最近的预测结果
HISTORY_LENGTH = 5 # 历史记录长度
CONFIDENCE_THRESHOLD = 0.8 # 置信度阈值
def speak_text_if_different(text, confidence):
"""只有当文本不同于上次播报且置信度足够高时才加入队列"""
global last_spoken_label, last_speak_time
current_time = time.time()
# 添加当前预测到历史
prediction_history.append((text, confidence))
if len(prediction_history) > HISTORY_LENGTH:
prediction_history.pop(0)
# 检查历史中是否一致
consistent = True
if len(prediction_history) == HISTORY_LENGTH:
first_label = prediction_history[0][0]
for label, _ in prediction_history[1:]:
if label != first_label:
consistent = False
break
# 条件判断:必须满足 时间间隔 + 内容不同 + 一致历史 + 置信度足够
if text and text not in ["未检测到手部", "未知手势"] and consistent and confidence >= CONFIDENCE_THRESHOLD:
if (current_time - last_speak_time > MIN_INTERVAL) and (text != last_spoken_label):
try:
speech_queue.put_nowait(text)
print(f"📢 已加入播报队列: {text} (置信度: {confidence:.2f})")
last_spoken_label = text # 更新为最新播报内容
last_speak_time = current_time # 更新时间戳
except queue.Full:
print("📢 语音队列已满,跳过")
elif text == last_spoken_label:
print(f"🔁 跳过重复词汇: {text}")
else:
print(f"⏱️ 间隔太短,暂不播报: {text}")
elif not consistent:
print(f"🔄 预测不一致,不播报: {text}")
# -------------------------------
# 辅助函数
# -------------------------------
def get_features(hand_landmarks):
"""从手部关键点提取特征向量 (21个关键点 * 2 = 42维)"""
features = []
for landmark in hand_landmarks.landmark:
features.append(landmark.x)
features.append(landmark.y)
return np.array(features)
def apply_ui_theme(image):
"""应用UI主题效果"""
# 创建半透明覆盖层
overlay = image.copy()
cv2.rectangle(overlay, (0, 0), (image.shape[1], image.shape[0]),
UI_COLORS['background'], -1)
alpha = 0.4 # 透明度
cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0, image)
return image
def put_chinese_text(img, text, position, font_size, color):
"""在图像上添加中文文本(使用PIL)"""
# 将OpenCV图像转换为PIL图像
img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
try:
# 尝试加载微软雅黑字体
font = ImageFont.truetype(FONT_PATH, font_size)
except:
# 如果加载失败,使用默认字体(可能不支持中文)
font = ImageFont.load_default()
print("警告: 微软雅黑字体未找到,使用默认字体(可能不支持中文)")
# 绘制文本
draw.text(position, text, font=font, fill=color)
# 将PIL图像转换回OpenCV格式
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
def draw_gui_elements(image, gesture_detected, predicted_text, confidence):
"""绘制GUI元素(支持中文)"""
# 绘制顶部信息栏
cv2.rectangle(image, (0, 0), (image.shape[1], 70), UI_COLORS['background'], -1)
# 应用UI主题
image = apply_ui_theme(image)
# 绘制标题(中文)
title = "手势识别系统"
image = put_chinese_text(image, title, (image.shape[1]//2 - 100, 15),
36, UI_COLORS['primary'])
# 绘制底部状态栏
cv2.rectangle(image, (0, image.shape[0]-50), (image.shape[1], image.shape[0]),
UI_COLORS['background'], -1)
status_text = f"模型: {MODEL_TYPE.upper()} v{MODEL_VERSION} | 按 'q' 退出 | 按 'r' 重置"
image = put_chinese_text(image, status_text, (10, image.shape[0]-40),
14, UI_COLORS['text'])
# 如果有手势检测结果
if gesture_detected:
# 绘制结果面板
cv2.rectangle(image, (10, 80), (350, 170), UI_COLORS['background'], -1)
cv2.rectangle(image, (10, 80), (350, 170), UI_COLORS['primary'], 2)
# 显示识别结果(中文)
result_text = f"识别结果: {predicted_text}"
image = put_chinese_text(image, result_text, (20, 95), 24, UI_COLORS['secondary'])
# 显示置信度(中文)
conf_text = f"置信度: {confidence:.2f}"
image = put_chinese_text(image, conf_text, (20, 130), 20, UI_COLORS['highlight'])
# 显示历史记录(中文)
history_text = f"上次播报: {last_spoken_label if last_spoken_label else '无'}"
image = put_chinese_text(image, history_text, (20, 165), 16, UI_COLORS['text'])
# 绘制置信度进度条
bar_width = int(300 * confidence)
cv2.rectangle(image, (20, 190), (20 + bar_width, 195), UI_COLORS['accent'], -1)
cv2.rectangle(image, (20, 190), (320, 195), UI_COLORS['text'], 1)
else:
# 没有检测到手部时显示友好的提示(中文)
guide_text = "请将手放入摄像头视野中"
image = put_chinese_text(image, guide_text,
(image.shape[1]//2 - 180, image.shape[0]//2 - 30),
30, UI_COLORS['secondary'])
# 绘制手部图标
center_x, center_y = image.shape[1]//2, image.shape[0]//2 + 50
cv2.circle(image, (center_x, center_y - 30), 20, UI_COLORS['accent'], 2)
cv2.line(image, (center_x - 15, center_y), (center_x + 15, center_y), UI_COLORS['accent'], 2)
for i in range(5):
angle = np.pi/2 - (i-2) * np.pi/8
length = 50
end_x = center_x + int(length * np.cos(angle))
end_y = center_y + int(length * np.sin(angle))
cv2.line(image, (center_x, center_y), (end_x, end_y), UI_COLORS['accent'], 2)
return image
# -------------------------------
# 主程序:手势识别 + 视频显示
# -------------------------------
cap = cv2.VideoCapture(0)
# 创建窗口并设置中文标题
window_name = 'Gesture Recognition System'
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, 1000, 700)
# 设置窗口中文标题(使用Windows API)
if os.name == 'nt':
set_window_title(window_name, "手势识别系统")
else:
cv2.setWindowTitle(window_name, "手势识别系统")
# 加载混淆矩阵可视化(如果存在)
confusion_matrix_path = os.path.join(PLOT_DIR, f'confusion_matrix_{MODEL_VERSION}.png')
confusion_matrix_img = None
if os.path.exists(confusion_matrix_path):
confusion_matrix_img = cv2.imread(confusion_matrix_path)
print(f"[INFO] 加载混淆矩阵可视化: {confusion_matrix_path}")
with mp_hands.Hands(
static_image_mode=False,
max_num_hands=1, # 只检测一只手
model_complexity=1, # 使用完整模型
min_detection_confidence=0.6,
min_tracking_confidence=0.6
) as hands:
while cap.isOpened():
success, image = cap.read()
if not success or image is None:
continue
image = cv2.flip(image, 1)
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
result = hands.process(rgb_image)
predicted_text = "未检测到手部"
confidence = 0.0
gesture_detected = False
if result.multi_hand_landmarks:
for hand_landmarks in result.multi_hand_landmarks:
gesture_detected = True
# 绘制手部关键点和连接线(使用自定义颜色)
mp_drawing.draw_landmarks(
image,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles.get_default_hand_landmarks_style(),
mp_drawing_styles.get_default_hand_connections_style()
)
# 提取特征并进行预测
try:
features = get_features(hand_landmarks)
# 使用整个pipeline进行预测(包括标准化)
pred_proba = model.predict_proba([features])[0]
pred_label_index = np.argmax(pred_proba)
confidence = pred_proba[pred_label_index]
# 获取预测标签对应的手势名称
predicted_text = classes[pred_label_index]
except Exception as e:
print(f"预测错误: {e}")
predicted_text = "识别错误"
confidence = 0.0
# 核心逻辑:只在一致且间隔够久时播报
speak_text_if_different(predicted_text, confidence)
# 应用UI设计(支持中文)
image = draw_gui_elements(image, gesture_detected, predicted_text, confidence)
# 显示图像
cv2.imshow(window_name, image)
# 如果存在混淆矩阵,显示在单独窗口
if confusion_matrix_img is not None:
# 调整混淆矩阵图像大小
resized_cm = cv2.resize(confusion_matrix_img, (600, 500))
cv2.imshow('模型性能评估', resized_cm)
# 退出条件
key = cv2.waitKey(1) & 0xFF
if key in [ord('q'), 27]: # 按q或ESC退出
break
elif key == ord('r'): # 按r重置语音播报状态
last_spoken_label = None
last_speak_time = 0
prediction_history = []
print("🔄 已重置语音播报状态")
elif key == ord('f'): # 按