import os
import time
import threading
import RPi.GPIO as GPIO
import cv2
import numpy as np
import pyaudio
import vosk
import json
import serial
import pynmea2
import datetime
import sqlite3
from pathlib import Path
import logging
import sys
# ------------------- 全局配置(整合两段代码核心参数) -------------------
# 1. 人脸识别/硬件控制配置
BUZZER_PIN = 18 # BCM模式
FREQ_LOW = 1000
FREQ_HIGH = 2000
BUZZER_DURATION = 60 # 蜂鸣器持续秒数
PIR_PIN = 17 # BCM模式
FACE_CASCADE_PATH = '/home/pi/.local/lib/python3.7/site-packages/cv2/data/haarcascade_frontalface_alt2.xml'
PROFILE_CASCADE_PATH = '/home/pi/.local/lib/python3.7/site-packages/cv2/data/haarcascade_profileface.xml'
VOSK_MODEL_PATH = "/home/pi/vosk-model-small-cn-0.22"
# 2. 北斗GPS配置
GPS_PORT = '/dev/ttyS0'
GPS_BAUDRATE = 9600
GPS_TIMEOUT = 2
GPS_DATA_DIR = Path("gps_data") # GPS数据存储目录
# 3. 全局状态变量(核心:控制流程衔接)
pwm = None
detection_triggered = False # 标记:是否检测到人/“救命”语音
buzzer_active = False # 标记:蜂鸣器是否运行
cap = None # 全局摄像头对象(避免重复创建)
face_cascade = None # 全局人脸分类器
profile_cascade = None # 全局侧脸分类器
gps_module = None # 全局GPS模块对象
gps_running = False # 标记:GPS是否正在运行
# ------------------- 1. 蜂鸣器控制(保留原逻辑) -------------------
def setup_buzzer():
global pwm
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(BUZZER_PIN, GPIO.OUT)
pwm = GPIO.PWM(BUZZER_PIN, FREQ_LOW)
def run_buzzer():
global buzzer_active, detection_triggered
buzzer_active = True
pwm.start(50)
start_time = time.time()
# 蜂鸣器运行条件:未超时、未被手动停止、未检测到人
while (time.time() - start_time < BUZZER_DURATION) and buzzer_active and not detection_triggered:
pwm.ChangeFrequency(FREQ_LOW)
time.sleep(0.5)
pwm.ChangeFrequency(FREQ_HIGH)
time.sleep(0.5)
pwm.stop()
buzzer_active = False
# ------------------- 2. 人脸识别+PIR检测(核心:检测到人触发GPS) -------------------
def setup_pir():
GPIO.setup(PIR_PIN, GPIO.IN)
def setup_camera_and_cascades():
"""初始化摄像头和人脸分类器(仅执行1次,减少资源占用)"""
global cap, face_cascade, profile_cascade
# 初始化摄像头并降低分辨率(提速)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("❌ 错误:摄像头无法打开!")
return False
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
# 加载人脸分类器
face_cascade = cv2.CascadeClassifier(FACE_CASCADE_PATH)
profile_cascade = cv2.CascadeClassifier(PROFILE_CASCADE_PATH)
if face_cascade.empty() or profile_cascade.empty():
print("❌ 错误:人脸分类器加载失败!")
return False
print("✅ 摄像头+人脸分类器初始化完成")
return True
def detect_human():
"""检测是否有人(正脸/侧脸),返回布尔值"""
global cap, face_cascade, profile_cascade
if not cap or not cap.isOpened():
return False
# 读取单帧图像(避免缓存堆积)
ret, img = cap.read()
if not ret:
return False
# 图像预处理(转灰度图,分类器必需)
img = cv2.flip(img, 1) # 镜像翻转(可选,不影响检测)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测正脸和侧脸(宽松参数减少漏检)
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))
profiles = profile_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))
# 打印检测结果(调试用)
if len(faces) > 0 or len(profiles) > 0:
print(f"🔍 检测到:正脸{len(faces)}个,侧脸{len(profiles)}个")
return len(faces) > 0 or len(profiles) > 0
def monitor_pir():
"""PIR红外检测:有活动时持续验证人脸,检测到则触发GPS启动"""
global detection_triggered, buzzer_active
print("🔄 PIR红外监控线程启动(等待活动...)")
while buzzer_active and not detection_triggered:
current_val = GPIO.input(PIR_PIN)
# PIR检测到移动(current_val=1)时,验证是否有人脸
if current_val == 1:
print("📢 PIR检测到活动,开始人脸验证...")
if detect_human():
detection_triggered = True
print("✅ 已检测到人,触发GPS启动!")
break
time.sleep(0.5) # 降低循环频率,减少CPU占用
print("🔚 PIR监控线程退出")
# ------------------- 3. 语音识别(保留“救命”触发逻辑) -------------------
def setup_voice_recognition():
"""初始化Vosk语音识别,返回识别器、音频流、音频对象"""
if not os.path.exists(VOSK_MODEL_PATH):
print("❌ 错误:Vosk模型目录不存在!")
return None, None, None
model = vosk.Model(VOSK_MODEL_PATH)
recognizer = vosk.KaldiRecognizer(model, 16000)
p = pyaudio.PyAudio()
stream = p.open(
format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True,
frames_per_buffer=4000,
input_device_index=None
)
print("✅ 语音识别初始化完成(支持“救命”触发)")
return recognizer, stream, p
def monitor_voice(recognizer, stream):
"""监控“救命”语音,识别到则触发GPS启动"""
global detection_triggered, buzzer_active
print("🎤 语音监控线程启动(等待“救命”指令...)")
while buzzer_active and not detection_triggered:
data = stream.read(4000, exception_on_overflow=False)
if recognizer.AcceptWaveform(data):
result = json.loads(recognizer.Result())
text = result.get("text", "").strip()
clean_text = text.replace(" ", "").replace("!", "").replace(",", "")
if "救命" in clean_text:
detection_triggered = True
print("🆘 识别到“救命”指令,触发GPS启动!")
break
print("🔚 语音监控线程退出")
# ------------------- 4. 北斗GPS定位(整合原逻辑,检测到人后启动) -------------------
class ATK1218GPS:
def __init__(self, port=GPS_PORT, baudrate=GPS_BAUDRATE, timeout=GPS_TIMEOUT):
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self.ser = None
self.db_conn = None
# 初始化GPS数据目录和日志
self._init_gps_dir_and_log()
# 初始化数据库
self.init_database()
print(f"📌 GPS模块配置:使用 {self.port} 串口")
def _init_gps_dir_and_log(self):
"""创建GPS数据目录并配置日志"""
GPS_DATA_DIR.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(GPS_DATA_DIR / "gps_log.log"),
logging.StreamHandler()
]
)
def check_dialout_permission(self):
"""检查用户是否在dialout组(串口访问必需)"""
try:
groups = os.popen('groups').read().strip()
if 'dialout' not in groups:
logging.warning("⚠️ 当前用户不在dialout组,可能无法访问GPS串口!")
logging.warning("⚠️ 请运行:sudo usermod -aG dialout $USER,然后重新登录")
return False
return True
except Exception as e:
logging.warning(f"⚠️ 检查权限出错:{e}")
return False
def connect(self):
"""连接GPS模块(检测到人后调用)"""
if not self.check_dialout_permission():
return False
try:
self.ser = serial.Serial(
self.port,
self.baudrate,
timeout=self.timeout,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
xonxoff=False,
rtscts=False
)
if self.ser.is_open:
logging.info("✅ GPS模块连接成功!")
return True
else:
logging.error("❌ 无法打开GPS串口")
return False
except PermissionError:
logging.error("❌ GPS串口权限不足(请检查dialout组)")
return False
except Exception as e:
logging.error(f"❌ GPS连接失败:{e}")
return False
def init_database(self):
"""初始化GPS数据存储数据库"""
try:
self.db_conn = sqlite3.connect(GPS_DATA_DIR / "location.db")
cursor = self.db_conn.cursor()
# 创建定位数据表(含速度、航向字段)
cursor.execute('''
CREATE TABLE IF NOT EXISTS locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME,
latitude REAL,
longitude REAL,
altitude REAL,
satellites INTEGER,
speed REAL,
course REAL,
fix_quality INTEGER
)
''')
self.db_conn.commit()
logging.info("✅ GPS数据库初始化完成")
except Exception as e:
logging.error(f"❌ GPS数据库初始化失败:{e}")
def convert_to_decimal(self, value, direction):
"""将GPS度分格式(DDMM.MMMM)转为十进制格式(DD.DDDDDD)"""
if not value:
return None
try:
val = float(value)
degrees = int(val // 100)
minutes = val % 100
decimal_degrees = degrees + minutes / 60
# 南纬(S)、西经(W)为负值
if direction in ['S', 'W']:
decimal_degrees = -decimal_degrees
return round(decimal_degrees, 6)
except:
return None
def parse_nmea_sentence(self, sentence):
"""解析NMEA数据(支持北斗/GPS双模)"""
try:
if not sentence.startswith('$'):
return None
msg = pynmea2.parse(sentence)
# 解析GGA数据(定位+海拔+卫星数)
if isinstance(msg, pynmea2.GGA):
return {
'timestamp': datetime.datetime.now(),
'latitude': self.convert_to_decimal(msg.lat, msg.lat_dir),
'longitude': self.convert_to_decimal(msg.lon, msg.lon_dir),
'altitude': float(msg.altitude) if msg.altitude else None,
'satellites': int(msg.num_sats) if msg.num_sats else None,
'speed': None,
'course': None,
'fix_quality': int(msg.gps_qual) if msg.gps_qual else 0
}
# 解析RMC数据(速度+航向)
elif isinstance(msg, pynmea2.RMC):
return {
'timestamp': datetime.datetime.now(),
'latitude': self.convert_to_decimal(msg.lat, msg.lat_dir),
'longitude': self.convert_to_decimal(msg.lon, msg.lon_dir),
'altitude': None,
'satellites': None,
'speed': float(msg.spd_over_grnd) if msg.spd_over_grnd else None,
'course': float(msg.true_course) if msg.true_course else None,
'fix_quality': 1 if msg.status == 'A' else 0
}
return None
except pynmea2.ParseError:
return None
except Exception as e:
logging.warning(f"⚠️ 解析NMEA数据出错:{e},数据:{sentence}")
return None
def save_location(self, location_data):
"""保存定位数据到数据库和文本文件"""
if not location_data or not self.db_conn:
return False
try:
# 保存到数据库
cursor = self.db_conn.cursor()
cursor.execute('''
INSERT INTO locations
(timestamp, latitude, longitude, altitude, satellites, speed, course, fix_quality)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
location_data['timestamp'],
location_data['latitude'],
location_data['longitude'],
location_data['altitude'],
location_data['satellites'],
location_data['speed'],
location_data['course'],
location_data['fix_quality']
))
self.db_conn.commit()
# 保存到文本文件(便于快速查看)
with open(GPS_DATA_DIR / "latest_location.txt", "w") as f:
f.write(f"定位时间:{location_data['timestamp']}\n")
f.write(f"纬度:{location_data['latitude']}\n")
f.write(f"经度:{location_data['longitude']}\n")
if location_data['altitude']:
f.write(f"海拔:{location_data['altitude']} 米\n")
if location_data['speed']:
f.write(f"速度:{location_data['speed']} 节\n")
f.write(f"航向:{location_data['course']} 度\n")
if location_data['satellites']:
f.write(f"卫星数量:{location_data['satellites']}\n")
f.write(f"定位质量:{location_data['fix_quality']}(1=有效定位)\n")
return True
except Exception as e:
logging.error(f"❌ 保存定位数据失败:{e}")
return False
def read_and_save_gps_data(self):
"""持续读取GPS数据并存储(检测到人后启动)"""
global gps_running
if not self.ser or not self.ser.is_open:
logging.error("❌ GPS未连接,无法读取数据")
return
gps_running = True
logging.info("🔄 开始读取GPS定位数据(按Ctrl+C停止)")
try:
while gps_running:
# 读取串口数据
data = self.ser.readline().decode('utf-8', errors='ignore').strip()
if data:
# 解析并保存有效定位数据
location_data = self.parse_nmea_sentence(data)
if location_data:
fix_quality = location_data.get('fix_quality', 0)
if isinstance(fix_quality, int) and fix_quality > 0:
logging.info(f"\n📌 定位成功:")
logging.info(f"时间:{location_data['timestamp']}")
logging.info(f"纬度:{location_data['latitude']} | 经度:{location_data['longitude']}")
if location_data['altitude']:
logging.info(f"海拔:{location_data['altitude']} 米")
self.save_location(location_data)
time.sleep(0.5) # 控制读取频率,减轻CPU负担
except KeyboardInterrupt:
logging.info("\n🔚 用户中断GPS数据读取")
except Exception as e:
logging.error(f"❌ GPS数据读取出错:{e}")
finally:
gps_running = False
def close(self):
"""关闭GPS串口和数据库连接"""
if self.ser and self.ser.is_open:
self.ser.close()
logging.info("✅ GPS串口已关闭")
if self.db_conn:
self.db_conn.close()
logging.info("✅ GPS数据库已关闭")
# ------------------- 5. 资源释放(统一管理,避免硬件占用) -------------------
def cleanup():
global buzzer_active, detection_triggered, cap, gps_running, gps_module
# 停止蜂鸣器
buzzer_active = False
# 停止GPS
gps_running = False
# 释放摄像头
if cap and cap.isOpened():
cap.release()
print("✅ 摄像头已释放")
# 释放GPIO
if pwm:
pwm.stop()
GPIO.cleanup()
cv2.destroyAllWindows()
print("✅ GPIO资源已释放")
# 释放语音识别
if 'stream' in globals() and 'p_audio' in globals():
stream.stop_stream()
stream.close()
p_audio.terminate()
print("✅ 语音识别资源已释放")
# 释放GPS
if gps_module:
gps_module.close()
print("🔚 所有资源已释放,程序退出")
# ------------------- 6. 主流程(人脸识别→GPS启动逻辑) -------------------
if __name__ == '__main__':
try:
# 1. 用户确认启动
user_input = input("请输入指令(1=启动人脸检测→GPS流程,其他=退出):")
if user_input.strip() != "1":
print("🔚 程序退出")
exit()
# 2. 初始化人脸识别相关硬件(顺序:GPIO→摄像头→语音)
setup_buzzer()
setup_pir()
if not setup_camera_and_cascades():
cleanup()
exit()
recognizer, stream, p_audio = setup_voice_recognition()
if not recognizer or not stream or not p_audio:
cleanup()
exit()
# 3. 启动人脸识别相关线程(蜂鸣器+PIR+语音)
detection_triggered = False
buzzer_active = True
buzzer_thread = threading.Thread(target=run_buzzer, daemon=True)
pir_thread = threading.Thread(target=monitor_pir, daemon=True)
voice_thread = threading.Thread(target=monitor_voice, args=(recognizer, stream), daemon=True)
buzzer_thread.start()
pir_thread.start()
voice_thread.start()
# 4. 主循环:等待检测到人,然后启动GPS
print("\n=====================================")
print("🔄 等待检测到人或“救命”指令(按Ctrl+C停止)")
print("=====================================")
while True:
# 检测到人/“救命”指令:启动GPS
if detection_triggered and not gps_running:
print("\n🚀 开始初始化GPS模块...")
gps_module = ATK1218GPS()
if gps_module.connect():
# 启动GPS数据读取线程(daemon=True:主程序退出时自动停止)
gps_thread = threading.Thread(target=gps_module.read_and_save_gps_data, daemon=True)
gps_thread.start()
else:
print("❌ GPS初始化失败,重试人脸检测...")
detection_triggered = False # 重置,重新等待人脸检测
# 持续运行(直到用户中断)
time.sleep(1)
except KeyboardInterrupt:
print("\n\n🔄 用户中断程序,开始释放资源...")
cleanup()
except Exception as e:
print(f"\n❌ 程序异常:{e}")
cleanup()
看看这个代码能不能满足先进行识别识别到有人之后再进行定位,没识别到有人就循环继续识别这个功能,你看看这段代码有什么缺陷?帮我订正一下