diff --git a/__pycache__/mjpeg_streamer.cpython-38.pyc b/__pycache__/mjpeg_streamer.cpython-38.pyc index 8fbbc65..f15d758 100644 Binary files a/__pycache__/mjpeg_streamer.cpython-38.pyc and b/__pycache__/mjpeg_streamer.cpython-38.pyc differ diff --git a/cali_web.py b/cali_web.py new file mode 100644 index 0000000..e69de29 diff --git a/images/back.png b/images/back.png index 6094940..d9b265b 100644 Binary files a/images/back.png and b/images/back.png differ diff --git a/images/front.png b/images/front.png index 5ed3e93..b23d78e 100644 Binary files a/images/front.png and b/images/front.png differ diff --git a/images/left.png b/images/left.png index 1c7e406..b51d090 100644 Binary files a/images/left.png and b/images/left.png differ diff --git a/images/right.png b/images/right.png index 812f51c..c951187 100644 Binary files a/images/right.png and b/images/right.png differ diff --git a/lj360_manager.sh b/lj360_manager.sh new file mode 100755 index 0000000..8f034a4 --- /dev/null +++ b/lj360_manager.sh @@ -0,0 +1,731 @@ +#!/bin/bash + +# ============================================= +# LJ360 全景监控服务管理脚本 - 增强版 +# 基于步骤化提示、彩色输出、友好交互 +# 适用于 RK3588 / Linux 本地或远程管理 +# ============================================= + +# --------------------------------------------- +# 初始化设置 +# --------------------------------------------- + +# 遇到错误立即退出 +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +PURPLE='\033[0;35m' +NC='\033[0m' # No Color + +# 进度与状态打印函数 +print_header() { + clear + echo -e "${PURPLE}" + echo "╔══════════════════════════════════════════════════════╗" + echo "║ LJ360 全景监控服务管理工具 (增强版) ║" + echo "║ RK3588 / Linux ║" + echo "╚══════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +print_step() { + echo -e "${BLUE}[STEP $1/$2]${NC} $3" +} + +print_success() { + echo -e "${GREEN}✓${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +print_error() { + echo -e "${RED}✗${NC} $1" +} + +print_progress() { + echo -e "${CYAN}[进度]${NC} $1" +} + +print_divider() { + echo -e "${BLUE}──────────────────────────────────────────────${NC}" +} + +# --------------------------------------------- +# 全局配置 +# --------------------------------------------- + +TOTAL_STEPS=9 +STEP=1 + +SERVICE_NAME="lj360.service" +WATCHDOG_SERVICE="lj360_watchdog.service" + +# 获取当前用户 +CURRENT_USER=$(whoami) + +# 检查是否以 root 运行 +if [ "$EUID" -eq 0 ]; then + print_error "❌ 请勿使用 root 用户运行此脚本。" + print_error "✅ 请使用以下命令运行(普通用户):" + print_error " bash $0" + exit 1 +fi + +# 项目目录 +PROJECT_DIR="/home/$CURRENT_USER/LJ360" + +print_header +print_success "🔧 当前用户: $CURRENT_USER" +print_success "📂 项目目录: $PROJECT_DIR" +echo "" + +# --------------------------------------------- +# 主菜单逻辑与步骤化功能 +# --------------------------------------------- + +main_menu() { + while true; do + show_status + + echo -e "${CYAN}🔧 请选择要执行的操作:${NC}" + print_divider + echo "1) 🛠️ 安装 LJ360 全景监控服务" + echo "2) 🛡️ 安装并启动看门狗服务" + echo "3) ▶️ 启动全景监控服务" + echo "4) ⏹️ 停止全景监控服务" + echo "5) 🔄 重启全景监控服务" + echo "6) 🔔 启用开机自启" + echo "7) 🚫 禁用开机自启" + echo "8) 📜 查看实时日志" + echo "9) 📊 查看系统状态" + echo "10) 🧹 卸载 LJ360 服务" + echo "0) 🚪 退出脚本" + print_divider + + read -p "👉 请输入选项 [0-10]: " choice + + case $choice in + 1) + STEP=1 + install_service + ;; + 2) + STEP=1 + install_watchdog_service + ;; + 3) + STEP=1 + start_service + ;; + 4) + STEP=1 + stop_service + ;; + 5) + STEP=1 + restart_service + ;; + 6) + STEP=1 + enable_service + ;; + 7) + STEP=1 + disable_service + ;; + 8) + STEP=1 + view_logs + ;; + 9) + STEP=1 + view_status + ;; + 10) + STEP=1 + uninstall_service + ;; + 0) + print_success "👋 再见!脚本已退出。" + exit 0 + ;; + *) + print_error "❌ 无效选项,请重新输入 [0-10]" + sleep 2 + ;; + esac + done +} + +# --------------------------------------------- +# 功能函数 +# --------------------------------------------- + +show_status() { + print_header + + echo -e "${BLUE}【📊 当前服务状态】${NC}" + print_divider + + # 检查主服务状态 + if systemctl is-active --quiet $SERVICE_NAME 2>/dev/null; then + echo -e "${GREEN}● LJ360 服务正在运行 ✅${NC}" + else + echo -e "${RED}● LJ360 服务未运行 ❌${NC}" + fi + + # 检查看门狗服务状态 + if systemctl is-active --quiet $WATCHDOG_SERVICE 2>/dev/null; then + echo -e "${GREEN}● 看门狗服务正在运行 🛡️${NC}" + else + echo -e "${YELLOW}● 看门狗服务未运行 ⚠${NC}" + fi + + # 检查开机自启状态 + if systemctl is-enabled $SERVICE_NAME 2>/dev/null; then + echo -e "${GREEN}● 开机自启已启用 ✅${NC}" + else + echo -e "${YELLOW}● 开机自启未启用 ⚠${NC}" + fi + + # 检查Web端口 + echo "" + echo -e "${BLUE}【🌐 网络端口状态】${NC}" + print_divider + if netstat -tlnp 2>/dev/null | grep -q ":5000"; then + echo -e "${GREEN}● Web 服务端口 5000 已监听 ✅${NC}" + else + echo -e "${RED}● Web 服务端口 5000 未监听 ❌${NC}" + fi + + echo "" +} + +# -------------------------- +# Step 1: 安装服务 +# -------------------------- +install_service() { + print_step $STEP $TOTAL_STEPS "安装 LJ360 全景监控服务" + + # 检查项目目录是否存在 + if [ ! -d "$PROJECT_DIR" ]; then + print_error "❌ 项目目录不存在: $PROJECT_DIR" + print_error "请确认项目路径是否正确" + read -p "🔘 按 Enter 继续..." -r + return + fi + + # 检查web.py文件是否存在 + if [ ! -f "$PROJECT_DIR/web.py" ]; then + print_error "❌ web.py 文件不存在: $PROJECT_DIR/web.py" + print_error "请确认项目文件是否正确" + read -p "🔘 按 Enter 继续..." -r + return + fi + + # 创建启动脚本 + local START_SCRIPT="$PROJECT_DIR/start_lj360.sh" + print_progress "🔧 创建启动脚本: $START_SCRIPT" + cat > "$START_SCRIPT" << EOF +#!/bin/bash +# LJ360 全景监控启动脚本 +# 项目目录: $PROJECT_DIR + +# 切换到项目目录 +cd "$PROJECT_DIR" + +# 设置环境变量 +export DISPLAY=:0 +export XAUTHORITY=/home/$CURRENT_USER/.Xauthority +export PYTHONPATH=$PROJECT_DIR:\$PYTHONPATH + +# 日志文件 +LOG_FILE="$PROJECT_DIR/lj360.log" +ERROR_LOG="$PROJECT_DIR/lj360_error.log" + +echo "=========================================" >> "\$LOG_FILE" +echo "LJ360 启动时间: \$(date)" >> "\$LOG_FILE" +echo "=========================================" >> "\$LOG_FILE" + +# 检查是否已有进程在运行 +if pgrep -f "python3 web.py" > /dev/null; then + echo "\$(date): LJ360 应用已在运行,跳过启动" >> "\$LOG_FILE" + exit 0 +fi + +# 启动应用并记录输出 +echo "\$(date): 启动 LJ360 web.py..." >> "\$LOG_FILE" +exec python3 web.py >> "\$LOG_FILE" 2>> "\$ERROR_LOG" +EOF + + chmod +x "$START_SCRIPT" + chown "$CURRENT_USER:$CURRENT_USER" "$START_SCRIPT" + print_success "✅ 启动脚本已创建并授权" + + # 创建 systemd 服务 + print_progress "🔧 创建 systemd 服务文件: /etc/systemd/system/$SERVICE_NAME" + sudo tee /etc/systemd/system/$SERVICE_NAME > /dev/null << EOF +[Unit] +Description=LJ360 Surround View Service +After=network.target graphical.target +Wants=network.target graphical.target + +[Service] +Type=simple +User=$CURRENT_USER +WorkingDirectory=$PROJECT_DIR +Environment="DISPLAY=:0" +Environment="XAUTHORITY=/home/$CURRENT_USER/.Xauthority" +Environment="PYTHONPATH=$PROJECT_DIR" +Environment="XDG_RUNTIME_DIR=/run/user/1000" +ExecStart=$PROJECT_DIR/start_lj360.sh +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +# 限制资源使用(可选) +# MemoryMax=1G +# CPUQuota=80% + +[Install] +WantedBy=multi-user.target +EOF + + sudo systemctl daemon-reload + print_success "✅ systemd 服务文件已部署 & 缓存已刷新" + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 2: 安装看门狗服务 +# -------------------------- +install_watchdog_service() { + print_step $STEP $TOTAL_STEPS "安装并启动看门狗服务" + + # 创建看门狗脚本 + local WATCHDOG_SCRIPT="$PROJECT_DIR/watchdog_lj360.sh" + print_progress "🔧 创建看门狗脚本: $WATCHDOG_SCRIPT" + cat > "$WATCHDOG_SCRIPT" << EOF +#!/bin/bash +# LJ360 看门狗服务脚本 +# 项目目录: $PROJECT_DIR + +APP_NAME="web.py" +CHECK_INTERVAL=10 +LOG_FILE="$PROJECT_DIR/watchdog.log" + +echo "=========================================" >> "\$LOG_FILE" +echo "看门狗服务启动时间: \$(date)" >> "\$LOG_FILE" +echo "=========================================" >> "\$LOG_FILE" + +while true; do + # 检查主服务进程是否存在 + if ! pgrep -f "python3 \$APP_NAME" > /dev/null; then + echo "\$(date): 检测到 LJ360 未运行,正在启动..." >> "\$LOG_FILE" + + cd "$PROJECT_DIR" + + # 设置环境变量 + export DISPLAY=:0 + export XAUTHORITY=/home/$CURRENT_USER/.Xauthority + export PYTHONPATH=$PROJECT_DIR:\$PYTHONPATH + + # 启动应用 + nohup python3 web.py >> "$PROJECT_DIR/lj360.log" 2>> "$PROJECT_DIR/lj360_error.log" & + + echo "\$(date): 已启动 LJ360,PID: \$!" >> "\$LOG_FILE" + + # 等待应用启动 + sleep 5 + fi + + sleep \$CHECK_INTERVAL +done +EOF + + chmod +x "$WATCHDOG_SCRIPT" + chown "$CURRENT_USER:$CURRENT_USER" "$WATCHDOG_SCRIPT" + print_success "✅ 看门狗脚本已创建并授权" + + # 创建看门狗 systemd 服务 + print_progress "🔧 创建看门狗服务文件: /etc/systemd/system/$WATCHDOG_SERVICE" + sudo tee /etc/systemd/system/$WATCHDOG_SERVICE > /dev/null << EOF +[Unit] +Description=LJ360 Watchdog Service +After=network.target +Wants=network.target + +[Service] +Type=simple +User=$CURRENT_USER +WorkingDirectory=$PROJECT_DIR +ExecStart=$PROJECT_DIR/watchdog_lj360.sh +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + + sudo systemctl daemon-reload + print_success "✅ 看门狗服务文件已部署" + + # 启用并启动看门狗服务 + print_progress "🚀 启动看门狗服务" + sudo systemctl enable $WATCHDOG_SERVICE + sudo systemctl start $WATCHDOG_SERVICE + sleep 2 + + if systemctl is-active --quiet $WATCHDOG_SERVICE; then + print_success "✅ 看门狗服务启动成功" + else + print_warning "⚠️ 看门狗服务启动可能有问题,请查看日志" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 3: 启动服务 +# -------------------------- +start_service() { + print_step $STEP $TOTAL_STEPS "启动 LJ360 全景监控服务" + + # 检查服务文件是否存在 + if [ ! -f "/etc/systemd/system/$SERVICE_NAME" ]; then + print_error "❌ 服务未安装,请先安装服务" + read -p "🔘 按 Enter 继续..." -r + return + fi + + print_progress "🚀 正在启动服务: $SERVICE_NAME" + sudo systemctl start "$SERVICE_NAME" + sleep 3 + + # 检查启动状态 + if systemctl is-active --quiet "$SERVICE_NAME"; then + print_success "✅ 服务启动成功" + + # 检查Web端口 + sleep 2 + if netstat -tlnp 2>/dev/null | grep -q ":5000"; then + local IP_ADDR=$(hostname -I | awk '{print $1}') + print_success "🌐 Web 服务已启动: http://$IP_ADDR:5000" + print_success "🌐 本地访问: http://localhost:5000" + fi + else + print_warning "⚠️ 服务启动可能有问题,请查看日志" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 4: 停止服务 +# -------------------------- +stop_service() { + print_step $STEP $TOTAL_STEPS "停止 LJ360 全景监控服务" + + # 检查服务文件是否存在 + if [ ! -f "/etc/systemd/system/$SERVICE_NAME" ]; then + print_error "❌ 服务未安装,请先安装服务" + read -p "🔘 按 Enter 继续..." -r + return + fi + + print_progress "⏹️ 正在停止服务: $SERVICE_NAME" + sudo systemctl stop "$SERVICE_NAME" + sleep 2 + + # 清理可能的残留进程 + print_progress "🧹 清理残留进程..." + pkill -f "python3 web.py" 2>/dev/null || true + + # 检查停止状态 + if ! systemctl is-active --quiet "$SERVICE_NAME"; then + print_success "✅ 服务停止成功" + else + print_warning "⚠️ 服务停止可能有问题" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 5: 重启服务 +# -------------------------- +restart_service() { + print_step $STEP $TOTAL_STEPS "重启 LJ360 全景监控服务" + + # 检查服务文件是否存在 + if [ ! -f "/etc/systemd/system/$SERVICE_NAME" ]; then + print_error "❌ 服务未安装,请先安装服务" + read -p "🔘 按 Enter 继续..." -r + return + fi + + print_progress "🔄 正在重启服务: $SERVICE_NAME" + sudo systemctl restart "$SERVICE_NAME" + sleep 3 + + # 检查重启状态 + if systemctl is-active --quiet "$SERVICE_NAME"; then + print_success "✅ 服务重启成功" + + # 检查Web端口 + sleep 2 + if netstat -tlnp 2>/dev/null | grep -q ":5000"; then + local IP_ADDR=$(hostname -I | awk '{print $1}') + print_success "🌐 Web 服务已重启: http://$IP_ADDR:5000" + fi + else + print_warning "⚠️ 服务重启可能有问题,请查看日志" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 6: 启用开机自启 +# -------------------------- +enable_service() { + print_step $STEP $TOTAL_STEPS "启用开机自启" + + # 检查服务文件是否存在 + if [ ! -f "/etc/systemd/system/$SERVICE_NAME" ]; then + print_error "❌ 服务未安装,请先安装服务" + read -p "🔘 按 Enter 继续..." -r + return + fi + + print_progress "🔔 正在启用开机自启: $SERVICE_NAME" + sudo systemctl enable "$SERVICE_NAME" + sleep 1 + + if systemctl is-enabled "$SERVICE_NAME"; then + print_success "✅ 开机自启已启用" + else + print_error "❌ 开机自启启用失败" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 7: 禁用开机自启 +# -------------------------- +disable_service() { + print_step $STEP $TOTAL_STEPS "禁用开机自启" + + # 检查服务文件是否存在 + if [ ! -f "/etc/systemd/system/$SERVICE_NAME" ]; then + print_error "❌ 服务未安装,请先安装服务" + read -p "🔘 按 Enter 继续..." -r + return + fi + + print_progress "🚫 正在禁用开机自启: $SERVICE_NAME" + sudo systemctl disable "$SERVICE_NAME" + sleep 1 + + if ! systemctl is-enabled "$SERVICE_NAME"; then + print_success "✅ 开机自启已禁用" + else + print_error "❌ 开机自启禁用失败" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 8: 查看日志 +# -------------------------- +view_logs() { + print_step $STEP $TOTAL_STEPS "查看实时日志" + + # 检查服务文件是否存在 + if [ ! -f "/etc/systemd/system/$SERVICE_NAME" ]; then + print_error "❌ 服务未安装,请先安装服务" + read -p "🔘 按 Enter 继续..." -r + return + fi + + print_progress "📜 请选择要查看的日志类型:" + echo "1) 📋 查看 systemd 服务日志" + echo "2) 📄 查看应用日志文件" + echo "3) ⚠️ 查看错误日志文件" + echo "4) 🛡️ 查看看门狗日志" + print_divider + + read -p "👉 请选择 [1-4]: " log_choice + + case $log_choice in + 1) + print_progress "📜 正在打开 systemd 服务日志(按 Ctrl+C 退出)" + echo "" + sudo journalctl -u "$SERVICE_NAME" -f + ;; + 2) + print_progress "📄 正在查看应用日志文件: $PROJECT_DIR/lj360.log" + echo "" + if [ -f "$PROJECT_DIR/lj360.log" ]; then + tail -50 "$PROJECT_DIR/lj360.log" + echo "" + echo "按 Enter 查看更多,或按 Ctrl+C 退出..." + tail -f "$PROJECT_DIR/lj360.log" + else + print_error "❌ 日志文件不存在" + fi + ;; + 3) + print_progress "⚠️ 正在查看错误日志文件: $PROJECT_DIR/lj360_error.log" + echo "" + if [ -f "$PROJECT_DIR/lj360_error.log" ]; then + tail -50 "$PROJECT_DIR/lj360_error.log" + else + print_warning "⚠️ 错误日志文件不存在" + fi + ;; + 4) + print_progress "🛡️ 正在查看看门狗日志文件: $PROJECT_DIR/watchdog.log" + echo "" + if [ -f "$PROJECT_DIR/watchdog.log" ]; then + tail -50 "$PROJECT_DIR/watchdog.log" + else + print_warning "⚠️ 看门狗日志文件不存在" + fi + ;; + *) + print_error "❌ 无效选项" + ;; + esac + + STEP=$((STEP + 1)) + read -p "🔘 日志查看结束,按 Enter 继续..." -r +} + +# -------------------------- +# Step 9: 查看系统状态 +# -------------------------- +view_status() { + print_step $STEP $TOTAL_STEPS "查看详细系统状态" + + echo -e "${CYAN}【📊 系统资源状态】${NC}" + print_divider + + # CPU使用率 + echo -e "${BLUE}● CPU 使用率:${NC}" + top -bn1 | grep "Cpu(s)" | awk '{print " " $2 "% user, " $4 "% system, " $8 "% idle"}' + + # 内存使用 + echo -e "${BLUE}● 内存使用:${NC}" + free -h | awk '/^Mem:/ {print " " $3 " / " $2 " (" $3/$2*100 "%) used"}' + + # 磁盘空间 + echo -e "${BLUE}● 磁盘空间:${NC}" + df -h /home | awk 'NR==2 {print " " $3 " / " $2 " (" $5 ") used"}' + + echo "" + echo -e "${CYAN}【🔍 进程状态】${NC}" + print_divider + + # LJ360相关进程 + if pgrep -f "python3 web.py" > /dev/null; then + echo -e "${GREEN}● LJ360 进程正在运行 ✅${NC}" + ps aux | grep "python3 web.py" | grep -v grep | awk '{print " PID: " $2 ", CPU: " $3 "%, MEM: " $4 "%, CMD: " $11}' + else + echo -e "${RED}● LJ360 进程未运行 ❌${NC}" + fi + + echo "" + echo -e "${CYAN}【📈 服务运行时间】${NC}" + print_divider + + if systemctl is-active --quiet $SERVICE_NAME; then + UPTIME=$(systemctl show $SERVICE_NAME --property=ActiveEnterTimestamp | awk -F= '{print $2}') + echo -e "${GREEN}● 服务启动时间: $UPTIME${NC}" + fi + + if systemctl is-active --quiet $WATCHDOG_SERVICE; then + WATCHDOG_UPTIME=$(systemctl show $WATCHDOG_SERVICE --property=ActiveEnterTimestamp | awk -F= '{print $2}') + echo -e "${GREEN}● 看门狗启动时间: $WATCHDOG_UPTIME${NC}" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# -------------------------- +# Step 10: 卸载服务 +# -------------------------- +uninstall_service() { + print_step $STEP $TOTAL_STEPS "卸载 LJ360 全景监控服务" + echo -e "${RED}⚠ 警告:此操作将卸载 LJ360 全景监控服务!${NC}" + echo -n "✅ 确定要卸载吗?(y/N): " + read -r confirm + if [[ $confirm =~ ^[Yy]$ ]]; then + print_progress "⏹️ 停止服务..." + sudo systemctl stop "$SERVICE_NAME" 2>/dev/null || true + sudo systemctl stop "$WATCHDOG_SERVICE" 2>/dev/null || true + + print_progress "🚫 禁用开机自启..." + sudo systemctl disable "$SERVICE_NAME" 2>/dev/null || true + sudo systemctl disable "$WATCHDOG_SERVICE" 2>/dev/null || true + + print_progress "🗑️ 删除 systemd 服务文件..." + sudo rm -f /etc/systemd/system/"$SERVICE_NAME" + sudo rm -f /etc/systemd/system/"$WATCHDOG_SERVICE" + sudo systemctl daemon-reload + sudo systemctl reset-failed + + print_progress "🗑️ 删除启动脚本..." + rm -f "$PROJECT_DIR/start_lj360.sh" + rm -f "$PROJECT_DIR/watchdog_lj360.sh" + + print_progress "🔪 清理进程..." + pkill -f "python3 web.py" 2>/dev/null || true + pkill -f "watchdog_lj360.sh" 2>/dev/null || true + + print_success "✅ LJ360 全景监控服务卸载完成" + print_warning "📝 日志文件保留在: $PROJECT_DIR/" + print_warning " - lj360.log" + print_warning " - lj360_error.log" + print_warning " - watchdog.log" + else + print_warning "❌ 取消卸载操作" + fi + + STEP=$((STEP + 1)) + read -p "🔘 按 Enter 继续..." -r +} + +# --------------------------------------------- +# 启动主菜单 +# --------------------------------------------- + +# 检查是否在正确的目录运行 +if [ ! -d "$PROJECT_DIR" ]; then + print_error "❌ 项目目录不存在: $PROJECT_DIR" + print_error "请确认项目路径是否正确,或者修改脚本中的 PROJECT_DIR 变量" + exit 1 +fi + +main_menu \ No newline at end of file diff --git a/mjpeg_streamer.py b/mjpeg_streamer.py index 8fa55d7..5dc26d4 100644 --- a/mjpeg_streamer.py +++ b/mjpeg_streamer.py @@ -1,4 +1,4 @@ -# mjpeg_streamer.py +# mjpeg_streamer.pynano import threading import time from flask import Flask, Response, render_template, request, redirect, session, url_for diff --git a/seial_test.py b/seial_test.py new file mode 100644 index 0000000..3a602e2 --- /dev/null +++ b/seial_test.py @@ -0,0 +1,37 @@ +import serial +import time + +# 配置串口(请根据实际情况修改设备名) +PORT = '/dev/ttyS0' # 或 '/dev/ttyUSB0' +BAUDRATE = 115200 + +try: + # 打开串口 + ser = serial.Serial(PORT, BAUDRATE, timeout=1) + print(f"✅ 已打开串口 {PORT}") + + # 发送数据 + data_to_send = b'123456\n' + + # 持续读取数据(注意:此时串口是打开的!) + print("📥 开始监听串口数据...") + while True: + ser.write(data_to_send) + print("📤 已发送数据") + try: + line = ser.readline() + if line: + print("接收到:", line.hex()) + except serial.SerialException as e: + print("❌ 串口异常:", e) + break + time.sleep(1) + +except serial.SerialException as e: + print(f"❌ 无法打开串口 {PORT}: {e}") +except KeyboardInterrupt: + print("\n🛑 用户中断") +finally: + if 'ser' in locals() and ser.is_open: + ser.close() + print("🔌 串口已关闭") \ No newline at end of file diff --git a/surround_view/__pycache__/param_settings.cpython-38.pyc b/surround_view/__pycache__/param_settings.cpython-38.pyc index 2a93ebc..cf139b7 100644 Binary files a/surround_view/__pycache__/param_settings.cpython-38.pyc and b/surround_view/__pycache__/param_settings.cpython-38.pyc differ diff --git a/surround_view/__pycache__/process_thread.cpython-38.pyc b/surround_view/__pycache__/process_thread.cpython-38.pyc index 8492872..3d088af 100644 Binary files a/surround_view/__pycache__/process_thread.cpython-38.pyc and b/surround_view/__pycache__/process_thread.cpython-38.pyc differ diff --git a/surround_view/param_settings.py b/surround_view/param_settings.py index 9c58bf8..45063f0 100644 --- a/surround_view/param_settings.py +++ b/surround_view/param_settings.py @@ -12,7 +12,7 @@ shift_h = 100 # 标定图案与车辆之间在水平和垂直方向上的间隙大小 inn_shift_w = 20 -inn_shift_h = 20 +inn_shift_h = 30 # 拼接图像的总宽度/高度 total_w = 300 + 2 * shift_w @@ -20,10 +20,10 @@ total_h = 350 + 2 * shift_h # 计算车辆在全景图中的位置 -xl = shift_w + 45 + inn_shift_w +xl = shift_w + 55 + inn_shift_w xr = total_w - xl print(xl, xr) -yt = shift_h + 60 + inn_shift_h +yt = shift_h + 55 + inn_shift_h yb = total_h - yt # -------------------------------------------------------------------- @@ -44,18 +44,18 @@ project_keypoints = { "back": [(shift_w + 0, shift_h), (shift_w + 300, shift_h), - (shift_w + 0, shift_h + 80), - (shift_w + 300, shift_h + 80)], + (shift_w + 0, shift_h + 70), + (shift_w + 300, shift_h + 70)], "left": [(shift_h + 0, shift_w), (shift_h + 350, shift_w), - (shift_h + 0, shift_w + 50), - (shift_h + 350, shift_w + 50)], + (shift_h + 0, shift_w + 40), + (shift_h + 350, shift_w + 40)], "right": [(shift_h + 0, shift_w), (shift_h + 350, shift_w), - (shift_h + 0, shift_w + 50), - (shift_h + 350, shift_w + 50)] + (shift_h + 0, shift_w + 40), + (shift_h + 350, shift_w + 40)] } car_image = cv2.imread(os.path.join(os.getcwd(), "images", "car.png")) diff --git a/test_dir/camera_v4l2 b/test_dir/camera_v4l2 new file mode 100755 index 0000000..84fdcde Binary files /dev/null and b/test_dir/camera_v4l2 differ diff --git a/test_dir/camera_v4l2.cpp b/test_dir/camera_v4l2.cpp new file mode 100644 index 0000000..2fce447 --- /dev/null +++ b/test_dir/camera_v4l2.cpp @@ -0,0 +1,53 @@ +#include +#include + +int main() { + // 使用 V4L2 后端打开摄像头(设备索引 0 对应 /dev/video0) + cv::VideoCapture cap(0, cv::CAP_V4L2); + cap.set(cv::CAP_PROP_FRAME_WIDTH, 1920); + cap.set(cv::CAP_PROP_FRAME_HEIGHT, 1080); + cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('Y','U','Y','V')); + // cap.set(cv::CAP_PROP_BUFFERSIZE, 1); // 减少缓冲,降低延迟 + + if (!cap.isOpened()) { + std::cerr << "❌ 无法打开摄像头 /dev/video0\n"; + return -1; + } + + // 可选:设置分辨率和格式(根据你的摄像头能力调整) + + + std::cout << "✅ 摄像头已打开,按 'q' 或 ESC 退出。\n"; + + cv::Mat frame; + while (true) { + cap >> frame; // 从摄像头读取一帧 + + if (frame.empty()) { + std::cerr << "⚠️ 读取帧失败,可能摄像头被占用或断开。\n"; + break; + } + + // 如果是 YUYV 格式(2通道),转换为 BGR 显示彩色 + cv::Mat display_frame; + if (frame.channels() == 2) { + cv::cvtColor(frame, display_frame, cv::COLOR_YUV2BGR_YUYV); + } else { + display_frame = frame; // 假设已经是 BGR + } + + // 显示图像 + cv::imshow("Camera Feed (V4L2)", display_frame); + + // 按 q 或 ESC 退出 + int key = cv::waitKey(1) & 0xFF; + if (key == 'q' || key == 27) { + break; + } + } + + cap.release(); + cv::destroyAllWindows(); + std::cout << "👋 已退出。\n"; + return 0; +} diff --git a/test_dir/check_opencv b/test_dir/check_opencv new file mode 100755 index 0000000..4e0e382 Binary files /dev/null and b/test_dir/check_opencv differ diff --git a/test_dir/v4l2.cpp b/test_dir/v4l2.cpp new file mode 100644 index 0000000..1e8c44b --- /dev/null +++ b/test_dir/v4l2.cpp @@ -0,0 +1,7 @@ +#include +#include + +int main() { + std::cout << cv::getBuildInformation() << std::endl; + return 0; +} diff --git a/test_dir/your_app b/test_dir/your_app new file mode 100755 index 0000000..8f3e9dd Binary files /dev/null and b/test_dir/your_app differ diff --git a/web.py b/web.py index 92be605..a45c11b 100644 --- a/web.py +++ b/web.py @@ -5,9 +5,65 @@ import sys import os import argparse import numpy as np +import serial +import time from surround_view import FisheyeCameraModel, BirdView import surround_view.param_settings as settings +# 串口雷达配置 +RADAR_SERIAL_PORT = '/dev/ttyS0' +RADAR_BAUDRATE = 115200 # 根据实际雷达配置调整 +RADAR_TIMEOUT = 0.1 + +# 距离阈值配置(单位:mm) +DISTANCE_THRESHOLD = 3000 # 距离过近阈值,可以根据需要调整 + +# 雷达ID与方向映射 +RADAR_ID_MAP = {1: 'front', 2: 'left', 3: 'back', 4: 'right'} + +def parse_radar_data(raw_data): + """ + 解析雷达原始数据 + 数据格式: 0xA5 0x5A [雷达ID-距离高字节-距离低字节]... 0x5A 0xA5 + """ + try: + # 查找帧头位置 + frame_start = raw_data.find(b'\xA5\x5A') + if frame_start == -1: + return None + + # 查找帧尾位置 + frame_end = raw_data.find(b'\x5A\xA5', frame_start) + if frame_end == -1: + return None + + # 提取完整帧数据 + frame_data = raw_data[frame_start+2:frame_end] + + # 检查数据长度是否正确(每个雷达3字节 × 4雷达 = 12字节) + if len(frame_data) != 12: + return None + + # 解析每个雷达数据 + radar_distances = {} + for i in range(4): + radar_id = frame_data[i*3] + if radar_id not in RADAR_ID_MAP: + continue + + distance_high = frame_data[i*3+1] + distance_low = frame_data[i*3+2] + distance = (distance_high << 8) + distance_low + + direction = RADAR_ID_MAP[radar_id] + radar_distances[direction] = distance + + return radar_distances + + except Exception as e: + print(f"[ERROR] 雷达数据解析失败: {e}") + return None + sys.path.append(os.path.dirname(__file__)) # 确保能导入 py_utils from py_utils.coco_utils import COCO_test_helper from py_utils.rknn_executor import RKNN_model_container # 假设使用 RKNN @@ -268,6 +324,17 @@ class MultiCameraBirdView: "left": False, "right": False } + + # 雷达数据 + self.radar_distances = { + "front": 0, + "back": 0, + "left": 0, + "right": 0 + } + self.radar_serial = None + self.radar_thread = None + self.radar_running = False # === 新增:YOLO 人体检测模型 === try: @@ -281,6 +348,9 @@ class MultiCameraBirdView: self.shared_buffer = SharedFrameBuffer() # 👈 新增 + # 初始化雷达串口 + self._init_radar_serial() + def overlay_alert(self, birdview_img): """在鸟瞰图上叠加半透明红色预警区域""" h, w = birdview_img.shape[:2] @@ -369,6 +439,63 @@ class MultiCameraBirdView: print(f"[ERROR] YOLO检测失败: {e}") return image, [], [] + def _init_radar_serial(self): + """ + 初始化雷达串口并启动数据读取线程 + """ + try: + self.radar_serial = serial.Serial( + port=RADAR_SERIAL_PORT, + baudrate=RADAR_BAUDRATE, + timeout=RADAR_TIMEOUT + ) + print(f"✅ 已打开雷达串口 {RADAR_SERIAL_PORT}") + + # 启动雷达数据读取线程 + self.radar_running = True + self.radar_thread = threading.Thread(target=self._read_radar_data, daemon=True) + self.radar_thread.start() + print("[INFO] 雷达数据读取线程已启动") + + except serial.SerialException as e: + print(f"❌ 无法打开雷达串口 {RADAR_SERIAL_PORT}: {e}") + self.radar_serial = None + except Exception as e: + print(f"[ERROR] 雷达初始化失败: {e}") + self.radar_serial = None + + def _read_radar_data(self): + """ + 雷达数据读取线程函数 + """ + buffer = b'' + while self.radar_running and self.radar_serial and self.radar_serial.is_open: + try: + # 读取可用数据 + if self.radar_serial.in_waiting > 0: + data = self.radar_serial.read(self.radar_serial.in_waiting) + buffer += data + + # 尝试解析数据 + parsed_data = parse_radar_data(buffer) + + if parsed_data: + print(f"[RADAR] 前:{parsed_data.get('front', 0):4d}cm 后:{parsed_data.get('back', 0):4d}cm 左:{parsed_data.get('left', 0):4d}cm 右:{parsed_data.get('right', 0):4d}cm") + + # 更新雷达距离数据 + self.radar_distances.update(parsed_data) + # 清空已解析的缓冲区 + buffer = buffer[buffer.find(b'\x5A\xA5')+2:] + + time.sleep(0.01) # 降低CPU占用 + + except serial.SerialException as e: + print(f"❌ 雷达串口异常: {e}") + break + except Exception as e: + print(f"[ERROR] 雷达数据读取失败: {e}") + time.sleep(0.1) + def _initialize_weights(self): try: images = [os.path.join(os.getcwd(), "images", name + ".png") for name in self.names] @@ -404,6 +531,9 @@ class MultiCameraBirdView: current_view = "front" frame_count = 0 detection_interval = 3 # 每5帧进行一次检测,避免性能问题 + + # 雷达触发视图切换的方向优先级(距离过近时优先显示哪个方向) + radar_priority = ['front', 'left', 'right', 'back'] while self.running: raw_frames = {} @@ -414,7 +544,7 @@ class MultiCameraBirdView: ret, frame = cap.read() raw_frames[name] = frame - # self.shared_buffer.update(raw_frames[current_view]) + p_frame = self.process_frame_once(frame, model) @@ -427,6 +557,18 @@ class MultiCameraBirdView: self.birdview.make_white_balance() self.birdview.copy_car_image() + # 检查雷达距离,自动切换视图 + auto_switch_view = None + for direction in radar_priority: + distance = self.radar_distances.get(direction, 0) + if distance > 0 and distance < DISTANCE_THRESHOLD: + auto_switch_view = direction + break + + if auto_switch_view: + current_view = auto_switch_view + print(f"[INFO] 雷达检测到{auto_switch_view}方向距离过近 ({self.radar_distances[auto_switch_view]}cm),自动切换视图") + # 获取单路图像(仅去畸变) single_img = self.process_frame_undistort( raw_frames[current_view], @@ -465,49 +607,23 @@ class MultiCameraBirdView: bird_resized = cv2.resize(birdview_with_alert, (w_bird, h_display)) single_resized = cv2.resize(single_img, (w_single, h_display)) display = np.hstack((bird_resized, single_resized)) - - - - - # 在显示窗口上添加状态信息 - # info_text = f"View: {current_view} | Persons detected: {len(person_boxes) if 'person_boxes' in locals() else 0}" - # cv2.putText(display, info_text, (10, 30), - # cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2) - + self.shared_buffer.update(display) # 全屏显示 cv2.namedWindow('Video', cv2.WND_PROP_FULLSCREEN) cv2.setWindowProperty('Video', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) cv2.imshow("Video", display) key = cv2.waitKey(1) & 0xFF - # if key == ord('q'): - # self.running = False - # break - # elif key == ord('1'): - # current_view = "front" - # elif key == ord('2'): - # current_view = "back" - # elif key == ord('3'): - # current_view = "left" - # elif key == ord('4'): - # current_view = "right" - # # 新增:预警控制 - # elif key == ord('5'): - # self.alerts["front"] = True - # elif key == ord('6'): - # self.alerts["back"] = True - # elif key == ord('7'): - # self.alerts["left"] = True - # elif key == ord('8'): - # self.alerts["right"] = True - # elif key == ord('0'): - # # 清除所有预警 - # for k in self.alerts: - # self.alerts[k] = False - # elif key == ord('d'): - # # 手动触发一次检测 - # single_img, person_boxes, person_scores = self.detect_persons(single_img) + + # 清理雷达资源 + self.radar_running = False + if self.radar_thread and self.radar_thread.is_alive(): + self.radar_thread.join(timeout=1.0) + if self.radar_serial and self.radar_serial.is_open: + self.radar_serial.close() + print("🔌 雷达串口已关闭") + for cap in self.caps: cap.release() cv2.destroyAllWindows() diff --git a/yaml/back.yaml b/yaml/back.yaml index 2d22aa4..7920995 100644 --- a/yaml/back.yaml +++ b/yaml/back.yaml @@ -4,14 +4,14 @@ camera_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ 5.3042094228516248e+02, 0., 9.5131550663475389e+02, 0., - 5.3026569308499541e+02, 5.3695007291032755e+02, 0., 0., 1. ] + data: [ 5.2332271672164040e+02, 0., 9.3044951906127255e+02, 0., + 5.2236471571534673e+02, 5.4345769772632696e+02, 0., 0., 1. ] dist_coeffs: !!opencv-matrix rows: 4 cols: 1 dt: d - data: [ -9.2062464083377798e-03, -6.3620029090856196e-03, - 3.3735324773401221e-03, -1.0289639180500810e-03 ] + data: [ -5.9498237920217710e-03, -1.0006293350185162e-03, + 5.3026728708821542e-03, -4.5615699893810256e-03 ] resolution: !!opencv-matrix rows: 2 cols: 1 @@ -21,10 +21,10 @@ project_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ -2.2036739433214386e-01, -5.4728752801607983e-01, - 4.0669019104227289e+02, -2.2188976198317060e-02, - -5.6384233390057015e-01, 3.2888161676725548e+02, - -8.4090277960475698e-05, -2.2171947701663608e-03, 1. ] + data: [ -3.9668895968930029e-01, -8.7369111224637641e-01, + 5.0478302196710462e+02, -6.4056023932043557e-02, + -8.8811555523036101e-01, 3.8160313264225226e+02, + -2.2060551352011550e-04, -3.5783374044860163e-03, 1. ] scale_xy: !!opencv-matrix rows: 2 cols: 1 @@ -34,4 +34,4 @@ shift_xy: !!opencv-matrix rows: 2 cols: 1 dt: f - data: [ -150., -100. ] + data: [ -150., -300. ] diff --git a/yaml/front.yaml b/yaml/front.yaml index 5aff58e..5770566 100644 --- a/yaml/front.yaml +++ b/yaml/front.yaml @@ -4,14 +4,14 @@ camera_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ 5.3382445956203196e+02, 0., 9.7253025945442369e+02, 0., - 5.3393792084343488e+02, 5.6249605531924215e+02, 0., 0., 1. ] + data: [ 5.3803744412978585e+02, 0., 9.5032314180353455e+02, 0., + 5.3783453341561233e+02, 5.3627020384755701e+02, 0., 0., 1. ] dist_coeffs: !!opencv-matrix rows: 4 cols: 1 dt: d - data: [ -1.5749135021037808e-02, 2.9390620422222835e-03, - -4.3176357910129585e-03, 1.3296605027646462e-03 ] + data: [ -1.6431143641380805e-02, -7.4804038255179171e-03, + 9.8754634365867712e-03, -4.6136179081623443e-03 ] resolution: !!opencv-matrix rows: 2 cols: 1 @@ -21,10 +21,10 @@ project_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ -1.8474101274061588e-01, -5.4968582653196851e-01, - 4.1680109927253847e+02, 9.7678250049304510e-03, - -4.9902821258073782e-01, 3.0304882941065989e+02, - 4.3706314843734903e-05, -2.1601287734653030e-03, 1. ] + data: [ -4.1308938086273067e-01, -1.2364750670379583e+00, + 6.1409164370210317e+02, 1.5629889919431476e-02, + -1.2402139306547297e+00, 4.4475253192373538e+02, + 1.6633363942594040e-04, -4.9162230937539594e-03, 1. ] scale_xy: !!opencv-matrix rows: 2 cols: 1 @@ -34,4 +34,4 @@ shift_xy: !!opencv-matrix rows: 2 cols: 1 dt: f - data: [ -150., -100. ] + data: [ -150., -300. ] diff --git a/yaml/left.yaml b/yaml/left.yaml index c277082..545806e 100644 --- a/yaml/left.yaml +++ b/yaml/left.yaml @@ -4,14 +4,14 @@ camera_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ 5.3576790643136087e+02, 0., 9.5266112763930198e+02, 0., - 5.3494293886479875e+02, 5.4089729625135715e+02, 0., 0., 1. ] + data: [ 5.3234956441219367e+02, 0., 1.0016510548547229e+03, 0., + 5.3229861100984783e+02, 5.4667741130363822e+02, 0., 0., 1. ] dist_coeffs: !!opencv-matrix rows: 4 cols: 1 dt: d - data: [ -1.1422407725638895e-02, -1.2103818796148216e-02, - 9.0774770002077006e-03, -2.8278270352926444e-03 ] + data: [ -2.1940734474311458e-02, 2.5390922658033822e-02, + -2.7187547373220645e-02, 9.2193950144527706e-03 ] resolution: !!opencv-matrix rows: 2 cols: 1 @@ -21,10 +21,10 @@ project_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ -2.0080056297490056e-01, -6.1187781235143224e-01, - 4.5434494858537062e+02, -2.9556627352318810e-03, - -5.0502143174738201e-01, 3.0816263946657051e+02, - -1.8229281242150158e-05, -2.1645728959083397e-03, 1. ] + data: [ -4.9054850784465642e-01, -1.3514497357056201e+00, + 7.0604177518052268e+02, 1.0522109557771796e-02, + -1.1178584461183012e+00, 4.4274468178801629e+02, + 8.2723610754286312e-05, -4.9130349378573015e-03, 1. ] scale_xy: !!opencv-matrix rows: 2 cols: 1 @@ -34,4 +34,4 @@ shift_xy: !!opencv-matrix rows: 2 cols: 1 dt: f - data: [ -80., -100. ] + data: [ -150., -300. ] diff --git a/yaml/right.yaml b/yaml/right.yaml index 7d6e04d..085aa11 100644 --- a/yaml/right.yaml +++ b/yaml/right.yaml @@ -4,14 +4,14 @@ camera_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ 5.3402108990030604e+02, 0., 9.2598444295282172e+02, 0., - 5.3455325152709827e+02, 5.7771767919091610e+02, 0., 0., 1. ] + data: [ 5.2209622748686536e+02, 0., 9.5144634774080509e+02, 0., + 5.2226989398345188e+02, 5.3766700145639288e+02, 0., 0., 1. ] dist_coeffs: !!opencv-matrix rows: 4 cols: 1 dt: d - data: [ -1.8724887233075402e-02, 6.4408558584901701e-03, - -5.2069636709412993e-03, 8.4815411645490968e-04 ] + data: [ -8.0625649755334261e-03, 1.9229042307457818e-02, + -1.6765217009295411e-02, 2.3943754181756584e-03 ] resolution: !!opencv-matrix rows: 2 cols: 1 @@ -21,10 +21,10 @@ project_matrix: !!opencv-matrix rows: 3 cols: 3 dt: d - data: [ -2.0592797222758674e-01, -6.8891188079230814e-01, - 4.5964514365855666e+02, 2.1763027489573539e-02, - -6.3287857963783611e-01, 3.4166476009354983e+02, - 1.3350446934563321e-04, -2.6387220529307219e-03, 1. ] + data: [ -2.5531960850826479e-01, -8.5968191881593725e-01, + 5.2529047158069864e+02, 2.8508251494131515e-02, + -6.9799661435239291e-01, 3.3028662032142546e+02, + 1.4612833761795866e-04, -3.1783223690618283e-03, 1. ] scale_xy: !!opencv-matrix rows: 2 cols: 1 @@ -34,4 +34,4 @@ shift_xy: !!opencv-matrix rows: 2 cols: 1 dt: f - data: [ -80., -100. ] + data: [ -80., -200. ]