feat: update
This commit is contained in:
parent
9f2f086cdf
commit
b223b32fb4
@ -10,7 +10,8 @@ sys.path.append("/sdcard")
|
||||
|
||||
|
||||
# 创建舵机对象 (GPIO47, 角度范围0-270度)
|
||||
servo = ServoController(gpio_pin=47, min_angle=0, max_angle=360, servo_range=360)
|
||||
servo = ServoController(gpio_pin=46, min_angle=0, max_angle=360, servo_range=360)
|
||||
servo = ServoController(gpio_pin=61, min_angle=0, max_angle=360, servo_range=360)
|
||||
|
||||
print("舵机测试开始...")
|
||||
|
||||
@ -42,5 +43,5 @@ print("舵机测试开始...")
|
||||
|
||||
# 360度舵机需要一直发! sg90。 0-180为顺(速度变小) 180-360为逆(速度变大)。
|
||||
while True:
|
||||
servo.set_angle(360)
|
||||
servo.set_angle(100)
|
||||
time.sleep(1)
|
||||
|
||||
@ -11,11 +11,14 @@ from state_detection import StateDetector
|
||||
# ========== 配置参数 ==========
|
||||
|
||||
# WiFi配置
|
||||
WIFI_SSID = "dongshengwu"
|
||||
#WIFI_SSID = "dongshengwu"
|
||||
#WIFI_PASSWORD = "wds666666"
|
||||
WIFI_SSID = "ZTE_969121"
|
||||
WIFI_PASSWORD = "wds666666"
|
||||
|
||||
# 服务器配置
|
||||
SERVER_HOST = "172.20.10.9"
|
||||
#SERVER_HOST = "172.20.10.9"
|
||||
SERVER_HOST = "192.168.0.21"
|
||||
SERVER_PORT = 8081
|
||||
API_PATH = "/api/detection/analyze"
|
||||
|
||||
|
||||
BIN
k230/HaveFace.wav
Normal file
BIN
k230/HaveFace.wav
Normal file
Binary file not shown.
Binary file not shown.
@ -16,4 +16,9 @@
|
||||
2. 检测人脸( 超过5s)
|
||||
3. 请求模型端( 情绪检测以及皮肤状态打分,注意时间戳)
|
||||
4. 保存到数据库( 图像数据 ,心情,皮肤状态)
|
||||
5. 判断时候状态不好! 然后发起语音对话
|
||||
5. 判断时候状态不好! 然后发起语音对话
|
||||
|
||||
|
||||
## 其他
|
||||
### 音频格式转换
|
||||
ffmpeg -y -i hello_24k.wav -ar 44100 -ac 1 -sample_fmt s16 hello.wav
|
||||
|
||||
547
k230/main.py
547
k230/main.py
@ -0,0 +1,547 @@
|
||||
# main.py
|
||||
# 人脸检测与状态分析主程序
|
||||
# 功能:实时人脸检测 -> 持续3秒触发情绪和皮肤状态检测 -> 输出结果
|
||||
# 适用于庐山派 K230-CanMV 开发板
|
||||
|
||||
import os
|
||||
import time
|
||||
import gc
|
||||
import sys
|
||||
from face_detect_module import FaceDetector
|
||||
from state_detection import StateDetector
|
||||
from audio_module import AudioPlayer
|
||||
import camera_module
|
||||
|
||||
# 音频播放相关导入
|
||||
from media.media import *
|
||||
from media.pyaudio import *
|
||||
import media.wave as wave
|
||||
|
||||
# ========== 配置参数 ==========
|
||||
|
||||
# WiFi 配置
|
||||
WIFI_SSID = "ZTE_969121"
|
||||
WIFI_PASSWORD = "wds666666"
|
||||
|
||||
# 服务器配置
|
||||
SERVER_HOST = "192.168.0.21"
|
||||
SERVER_PORT = 8081
|
||||
API_PATH = "/api/detection/analyze"
|
||||
|
||||
# 检测参数
|
||||
DETECTION_THRESHOLD = 3.0 # 人脸持续3秒后触发检测
|
||||
COOLDOWN_PERIOD = 10.0 # 检测完成后10秒冷却期(避免频繁检测)
|
||||
|
||||
# 显示和音频配置
|
||||
DISPLAY_MODE = "lcd" # LCD显示模式
|
||||
AUDIO_FILE = "/sdcard/HaveFace.wav" # 检测到人脸时的提示音
|
||||
|
||||
# 摄像头配置
|
||||
DETECTION_IMAGE_WIDTH = 640
|
||||
DETECTION_IMAGE_HEIGHT = 480
|
||||
|
||||
# ========== 状态管理类 ==========
|
||||
|
||||
|
||||
class FaceDetectionState:
|
||||
"""
|
||||
人脸检测状态管理
|
||||
|
||||
追踪:
|
||||
- 人脸首次出现时间
|
||||
- 上次检测触发时间
|
||||
- 是否正在检测中
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.face_start_time = None # 人脸首次出现的时间戳(None表示无人脸)
|
||||
self.last_detection_time = None # 上次触发检测的时间戳
|
||||
self.is_detecting = False # 是否正在执行检测中
|
||||
|
||||
def on_face_detected(self):
|
||||
"""
|
||||
人脸检测到时调用(有人脸)
|
||||
"""
|
||||
if self.face_start_time is None:
|
||||
self.face_start_time = time.time()
|
||||
|
||||
def on_face_lost(self):
|
||||
"""
|
||||
人脸丢失时调用(无人脸)
|
||||
重置人脸计时器
|
||||
"""
|
||||
self.face_start_time = None
|
||||
|
||||
def get_face_duration(self):
|
||||
"""
|
||||
获取人脸持续时间(秒)
|
||||
|
||||
返回:
|
||||
float: 人脸持续的秒数,无人脸时返回0
|
||||
"""
|
||||
if self.face_start_time is None:
|
||||
return 0.0
|
||||
return time.time() - self.face_start_time
|
||||
|
||||
def should_trigger_detection(
|
||||
self,
|
||||
threshold=DETECTION_THRESHOLD,
|
||||
cooldown=COOLDOWN_PERIOD):
|
||||
"""
|
||||
判断是否应该触发检测
|
||||
|
||||
条件:
|
||||
1. 人脸持续时间 >= threshold
|
||||
2. 不在检测中
|
||||
3. 超过冷却期(避免重复检测)
|
||||
|
||||
参数:
|
||||
threshold: 触发检测的时间阈值(秒)
|
||||
cooldown: 冷却期(秒)
|
||||
|
||||
返回:
|
||||
bool: 是否应该触发检测
|
||||
"""
|
||||
# 如果正在检测中,不再触发
|
||||
if self.is_detecting:
|
||||
return False
|
||||
|
||||
# 人脸持续时间不足,不触发
|
||||
if self.get_face_duration() < threshold:
|
||||
return False
|
||||
|
||||
# 检查冷却期
|
||||
if self.last_detection_time is not None:
|
||||
time_since_last = time.time() - self.last_detection_time
|
||||
if time_since_last < cooldown:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def start_detection(self):
|
||||
"""标记开始执行检测"""
|
||||
self.is_detecting = True
|
||||
|
||||
def end_detection(self):
|
||||
"""标记检测完成,记录时间用于冷却期计算"""
|
||||
self.is_detecting = False
|
||||
self.last_detection_time = time.time()
|
||||
|
||||
def has_active_face(self):
|
||||
"""是否有活跃人脸(人脸出现过)"""
|
||||
return self.face_start_time is not None
|
||||
|
||||
|
||||
# ========== 辅助函数 ==========
|
||||
|
||||
def exit_check():
|
||||
"""
|
||||
检查退出信号(参考boot.py)
|
||||
"""
|
||||
try:
|
||||
os.exitpoint()
|
||||
except KeyboardInterrupt as e:
|
||||
print("[用户] 中断: {}".format(e))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def play_audio_safe(filename):
|
||||
"""
|
||||
安全播放音频文件(完全参考boot.py实现)
|
||||
|
||||
特点:
|
||||
- 完整播放音频
|
||||
- 正确初始化和释放资源
|
||||
- 支持用户中断
|
||||
- 参考 boot.py 的实现方式
|
||||
|
||||
参数:
|
||||
filename: WAV文件路径
|
||||
|
||||
返回:
|
||||
bool: True成功, False失败
|
||||
"""
|
||||
try:
|
||||
# 打开wav文件
|
||||
wf = wave.open(filename, 'rb')
|
||||
chunk = int(wf.get_framerate() / 25)
|
||||
|
||||
# 初始化PyAudio
|
||||
p = PyAudio()
|
||||
p.initialize(chunk)
|
||||
MediaManager.init() # vb buffer初始化(重要!)
|
||||
|
||||
# 创建音频输出流,设置的音频参数均为wave中获取到的参数
|
||||
stream = p.open(
|
||||
format=p.get_format_from_width(wf.get_sampwidth()),
|
||||
channels=wf.get_channels(),
|
||||
rate=wf.get_framerate(),
|
||||
output=True,
|
||||
frames_per_buffer=chunk
|
||||
)
|
||||
|
||||
# 从wav文件中读取第一帧数据
|
||||
data = wf.read_frames(chunk)
|
||||
|
||||
# 逐帧读取并播放
|
||||
while data:
|
||||
stream.write(data) # 将帧数据写入到音频输出流中
|
||||
data = wf.read_frames(chunk) # 从wav文件中读取数一帧数据
|
||||
if exit_check(): # 检查退出信号
|
||||
break
|
||||
|
||||
return True
|
||||
|
||||
except BaseException as e:
|
||||
print("[错误] 音频播放异常: {}".format(e))
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理资源
|
||||
try:
|
||||
stream.stop_stream() # 停止音频输出流
|
||||
stream.close() # 关闭音频输出流
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
p.terminate() # 释放音频对象
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
wf.close() # 关闭wav文件
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
MediaManager.deinit() # 释放vb buffer
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def image_to_rgb888(img):
|
||||
"""
|
||||
将图像转换为RGB888格式的bytes数据
|
||||
|
||||
支持多种Image对象格式(兼容不同的K230库版本)
|
||||
|
||||
参数:
|
||||
img: Image对象
|
||||
|
||||
返回:
|
||||
bytes: RGB888格式的图像数据
|
||||
转换失败返回 None
|
||||
"""
|
||||
try:
|
||||
if hasattr(img, 'to_bytes'):
|
||||
return img.to_bytes()
|
||||
if hasattr(img, 'tobytes'):
|
||||
return img.tobytes()
|
||||
return bytes(img)
|
||||
except Exception as e:
|
||||
print("[错误] 图像转换错误: {}".format(e))
|
||||
return None
|
||||
|
||||
|
||||
def initialize():
|
||||
"""
|
||||
初始化程序
|
||||
|
||||
步骤:
|
||||
1. 连接 WiFi(重试3次)
|
||||
2. 初始化 StateDetector
|
||||
3. 初始化 FaceDetector
|
||||
|
||||
返回:
|
||||
tuple: (face_detector, state_detector)
|
||||
初始化失败返回 (None, None)
|
||||
"""
|
||||
print("=" * 50)
|
||||
print("程序启动 - 人脸检测与状态分析系统")
|
||||
print("=" * 50)
|
||||
|
||||
# ===== 第1步:连接WiFi =====
|
||||
print("\n[初始化] 连接WiFi: {}".format(WIFI_SSID))
|
||||
|
||||
state_detector = StateDetector(SERVER_HOST, SERVER_PORT, API_PATH)
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
state_detector.connect_wifi(WIFI_SSID, WIFI_PASSWORD)
|
||||
print("[初始化] WiFi连接成功")
|
||||
break
|
||||
except Exception as e:
|
||||
print("[初始化] WiFi连接失败 (尝试 {}/3): {}".format(attempt + 1, e))
|
||||
if attempt < 2:
|
||||
time.sleep(2)
|
||||
else:
|
||||
print("[错误] WiFi连接失败,程序退出")
|
||||
return None, None
|
||||
|
||||
# ===== 第2步:初始化人脸检测器 =====
|
||||
print("[初始化] 启动人脸检测器...")
|
||||
|
||||
try:
|
||||
face_detector = FaceDetector(
|
||||
display_mode=DISPLAY_MODE,
|
||||
confidence_threshold=0.5,
|
||||
debug_mode=0
|
||||
)
|
||||
face_detector.start()
|
||||
print("[初始化] 人脸检测器启动成功")
|
||||
except Exception as e:
|
||||
print("[错误] 人脸检测器启动失败: {}".format(e))
|
||||
state_detector.disconnect()
|
||||
return None, None
|
||||
|
||||
print("\n[初始化] 初始化完成,开始检测...\n")
|
||||
|
||||
return face_detector, state_detector
|
||||
|
||||
|
||||
def capture_detection_image():
|
||||
"""
|
||||
捕获用于状态检测的图像(640x480)
|
||||
|
||||
由于 FaceDetector 使用摄像头的 1920x1080 分辨率,
|
||||
而状态检测需要 640x480 的图像,
|
||||
此函数会临时停止 FaceDetector,使用 camera_module 快速拍摄。
|
||||
|
||||
返回:
|
||||
bytes: RGB888 格式的图像数据
|
||||
失败返回 None
|
||||
"""
|
||||
try:
|
||||
# 初始化 640x480 摄像头
|
||||
camera_module.camera_init(DETECTION_IMAGE_WIDTH, DETECTION_IMAGE_HEIGHT)
|
||||
camera_module.camera_start()
|
||||
|
||||
# 等待摄像头稳定(参考 05test_state_detection.py)
|
||||
time.sleep(1)
|
||||
|
||||
# 拍照
|
||||
print("[检测] 拍照中...")
|
||||
img = camera_module.camera_snapshot()
|
||||
|
||||
# 转换为RGB888格式
|
||||
rgb888_data = image_to_rgb888(img)
|
||||
|
||||
# 清理摄像头资源
|
||||
camera_module.camera_stop()
|
||||
camera_module.camera_deinit()
|
||||
|
||||
return rgb888_data
|
||||
|
||||
except Exception as e:
|
||||
print("[错误] 图像捕获失败: {}".format(e))
|
||||
try:
|
||||
camera_module.camera_stop()
|
||||
camera_module.camera_deinit()
|
||||
except BaseException:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def trigger_detection(face_detector, state_detector):
|
||||
"""
|
||||
触发状态检测的完整流程
|
||||
|
||||
步骤:
|
||||
1. 暂停人脸检测显示(屏幕会冻结)
|
||||
2. 播放提示音
|
||||
3. 捕获 640x480 图像
|
||||
4. 发送状态检测请求(情绪+皮肤)
|
||||
5. 输出结果
|
||||
6. 恢复人脸检测显示
|
||||
|
||||
参数:
|
||||
face_detector: FaceDetector 实例
|
||||
state_detector: StateDetector 实例
|
||||
|
||||
注意:
|
||||
此过程会阻塞 3-5 秒,期间屏幕显示会冻结
|
||||
"""
|
||||
print("\n" + "=" * 50)
|
||||
print("触发状态检测...")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# 暂停人脸检测
|
||||
face_detector.stop()
|
||||
print("[检测] 暂停人脸显示")
|
||||
|
||||
# ===== 步骤1:播放提示音 =====
|
||||
print("[检测] 播放提示音...")
|
||||
try:
|
||||
if play_audio_safe(AUDIO_FILE):
|
||||
print("[检测] 提示音播放完成")
|
||||
else:
|
||||
print("[警告] 播放音频失败")
|
||||
except Exception as e:
|
||||
print("[警告] 播放音频异常: {}".format(e))
|
||||
|
||||
# ===== 步骤2:捕获图像 =====
|
||||
print("[检测] 捕获图像...")
|
||||
rgb888_data = capture_detection_image()
|
||||
|
||||
if rgb888_data is None:
|
||||
print("[错误] 无法获取图像数据,检测失败")
|
||||
return
|
||||
|
||||
print("[检测] 图像数据大小: {} bytes".format(len(rgb888_data)))
|
||||
|
||||
# ===== 步骤3:发送检测请求 =====
|
||||
print("[检测] 发送状态检测请求...")
|
||||
result = state_detector.detect(
|
||||
rgb888_data,
|
||||
width=DETECTION_IMAGE_WIDTH,
|
||||
height=DETECTION_IMAGE_HEIGHT
|
||||
)
|
||||
|
||||
# ===== 步骤4:输出结果 =====
|
||||
print("\n" + "=" * 45 + " 检测结果 " + "=" * 45)
|
||||
|
||||
if result.get("success"):
|
||||
emotion = result.get('emotion', 'unknown')
|
||||
confidence = result.get('emotion_confidence', 0)
|
||||
skin = result.get('skin_status', {})
|
||||
|
||||
print("情绪: {} (置信度: {:.2f})".format(emotion, confidence))
|
||||
print("皮肤状态:")
|
||||
print(" - 痘痘: {}".format(skin.get('acne', 0)))
|
||||
print(" - 皱纹: {}".format(skin.get('wrinkles', 0)))
|
||||
print(" - 毛孔: {}".format(skin.get('pores', 0)))
|
||||
print(" - 黑眼圈: {}".format(skin.get('dark_circles', 0)))
|
||||
else:
|
||||
print("检测失败: {}".format(result.get('error', '未知错误')))
|
||||
|
||||
print("=" * 100)
|
||||
|
||||
except Exception as e:
|
||||
print("[错误] 检测过程异常: {}".format(e))
|
||||
try:
|
||||
sys.print_exception(e)
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
finally:
|
||||
# 恢复人脸检测显示
|
||||
print("\n[检测] 恢复人脸检测...")
|
||||
try:
|
||||
face_detector.start()
|
||||
print("[检测] 人脸检测已恢复\n")
|
||||
except Exception as e:
|
||||
print("[错误] 恢复人脸检测失败: {}".format(e))
|
||||
|
||||
|
||||
def main_loop(face_detector, state_detector):
|
||||
"""
|
||||
主检测循环
|
||||
|
||||
流程:
|
||||
1. 检测人脸
|
||||
2. 更新状态(出现/消失)
|
||||
3. 判断是否触发检测
|
||||
4. 绘制和显示
|
||||
5. 垃圾回收
|
||||
|
||||
参数:
|
||||
face_detector: FaceDetector 实例
|
||||
state_detector: StateDetector 实例
|
||||
"""
|
||||
state = FaceDetectionState()
|
||||
frame_count = 0
|
||||
|
||||
while True:
|
||||
try:
|
||||
os.exitpoint()
|
||||
|
||||
# ===== 步骤1:检测人脸 =====
|
||||
img, faces = face_detector.detect()
|
||||
frame_count += 1
|
||||
|
||||
# ===== 步骤2:更新状态 =====
|
||||
if faces:
|
||||
state.on_face_detected()
|
||||
|
||||
# 仅每秒打印一次(减少日志输出)
|
||||
if frame_count % 25 == 0:
|
||||
print("[检测] 检测到 {} 个人脸,持续时间: {:.1f} 秒".format(
|
||||
len(faces), state.get_face_duration()
|
||||
))
|
||||
else:
|
||||
state.on_face_lost()
|
||||
|
||||
# ===== 步骤3:判断是否触发检测 =====
|
||||
if state.should_trigger_detection():
|
||||
state.start_detection()
|
||||
trigger_detection(face_detector, state_detector)
|
||||
state.end_detection()
|
||||
|
||||
# ===== 步骤4:绘制和显示 =====
|
||||
face_detector.draw_boxes()
|
||||
face_detector.show()
|
||||
|
||||
# ===== 步骤5:垃圾回收 =====
|
||||
gc.collect()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n[用户] 用户停止")
|
||||
break
|
||||
except Exception as e:
|
||||
print("[错误] 主循环异常: {}".format(e))
|
||||
try:
|
||||
sys.print_exception(e)
|
||||
except BaseException:
|
||||
pass
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
# ========== 主程序入口 ==========
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.exitpoint(os.EXITPOINT_ENABLE)
|
||||
|
||||
face_detector = None
|
||||
state_detector = None
|
||||
|
||||
try:
|
||||
# 初始化
|
||||
face_detector, state_detector = initialize()
|
||||
|
||||
# 启动主循环
|
||||
if face_detector and state_detector:
|
||||
main_loop(face_detector, state_detector)
|
||||
else:
|
||||
print("[错误] 初始化失败,程序退出")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n[用户] 用户中断")
|
||||
except Exception as e:
|
||||
print("[错误] 程序异常: {}".format(e))
|
||||
try:
|
||||
sys.print_exception(e)
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
finally:
|
||||
# 清理资源
|
||||
print("\n[清理] 释放资源...")
|
||||
|
||||
if face_detector:
|
||||
try:
|
||||
face_detector.stop()
|
||||
print("[清理] 人脸检测器已停止")
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
if state_detector:
|
||||
try:
|
||||
state_detector.disconnect()
|
||||
print("[清理] WiFi已断开")
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
print("[清理] 程序退出")
|
||||
28
k230/servo.py
Normal file
28
k230/servo.py
Normal file
@ -0,0 +1,28 @@
|
||||
# 02test_servo.py
|
||||
# 舵机测试程序:最大 -> 最小 -> 中间
|
||||
# 适用于庐山派 K230-CanMV 开发板
|
||||
|
||||
from servo_module import ServoController
|
||||
import time
|
||||
import sys
|
||||
|
||||
sys.path.append("/sdcard")
|
||||
|
||||
|
||||
# 俯仰角
|
||||
servo1 = ServoController(gpio_pin=42, min_angle=45, max_angle=225, servo_range=270)
|
||||
# 横滚
|
||||
servo2 = ServoController(gpio_pin=52, min_angle=45, max_angle=225, servo_range=270)
|
||||
print("舵机测试开始...")
|
||||
|
||||
try:
|
||||
servo1.set_angle(200)
|
||||
servo2.set_angle(135)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("用户停止")
|
||||
finally:
|
||||
servo1.deinit()
|
||||
print("舵机资源已释放")
|
||||
Loading…
x
Reference in New Issue
Block a user