{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "80978b97", "metadata": {}, "outputs": [], "source": [ "import time\n", "import cv2\n", "import numpy as np\n", "from mss import mss\n", "from collections import deque" ] }, { "cell_type": "code", "execution_count": 2, "id": "ea32fa13", "metadata": {}, "outputs": [], "source": [ "from typing import Callable, Optional" ] }, { "cell_type": "code", "execution_count": 3, "id": "27c4af4c", "metadata": {}, "outputs": [], "source": [ "import json\n", "import threading\n", "import websocket\n", "from queue import Queue\n", "\n", "class WebSocketClient:\n", " def __init__(self, server_url):\n", " self.server_url = server_url\n", " self.ws = None\n", " self.connected = False\n", " self.message_queue = Queue()\n", " self.lock = threading.Lock()\n", " self.reconnect_delay = 3 # 重连延迟(秒)\n", " self.running = False\n", "\n", " def on_open(self, ws):\n", " \"\"\"连接成功回调\"\"\"\n", " print(\"WebSocket 连接成功\")\n", " self.connected = True\n", "\n", " def on_message(self, ws, message):\n", " \"\"\"接收消息回调\"\"\"\n", " print(f\"收到前端消息: {message}\")\n", "\n", " def on_error(self, ws, error):\n", " \"\"\"错误回调\"\"\"\n", " print(f\"WebSocket 错误: {error}\")\n", " self.connected = False\n", "\n", " def on_close(self, ws, close_status_code, close_msg):\n", " \"\"\"关闭回调\"\"\"\n", " print(f\"WebSocket 关闭: {close_status_code} - {close_msg}\")\n", " self.connected = False\n", " if self.running:\n", " self.reconnect()\n", "\n", " def reconnect(self):\n", " \"\"\"断线重连\"\"\"\n", " print(f\"{self.reconnect_delay}秒后尝试重连...\")\n", " time.sleep(self.reconnect_delay)\n", " self.connect()\n", "\n", " def connect(self):\n", " \"\"\"连接WebSocket服务器\"\"\"\n", " self.running = True\n", " self.ws = websocket.WebSocketApp(\n", " self.server_url,\n", " on_open=self.on_open,\n", " on_message=self.on_message,\n", " on_error=self.on_error,\n", " on_close=self.on_close\n", " )\n", " # 在独立线程中运行WebSocket\n", " threading.Thread(target=self.ws.run_forever, daemon=True).start()\n", "\n", " def send(self, data):\n", " \"\"\"发送消息(线程安全)\"\"\"\n", " with self.lock:\n", " if self.connected and self.ws:\n", " try:\n", " self.ws.send(\"xtleto|\"+json.dumps(data))\n", " return True\n", " except Exception as e:\n", " print(f\"消息发送失败: {e}\")\n", " return False\n", " return False\n", "\n", " def stop(self):\n", " \"\"\"停止WebSocket连接\"\"\"\n", " self.running = False\n", " if self.ws:\n", " self.ws.close()" ] }, { "cell_type": "code", "execution_count": 4, "id": "88cabf89", "metadata": {}, "outputs": [], "source": [ "class DynamicAdjuster:\n", " def __init__(self):\n", " # 参数跟踪窗口(保留最近20帧数据)\n", " self.diff_history = deque(maxlen=20)\n", " self.area_history = deque(maxlen=20)\n", " \n", " # 初始参数值\n", " self.threshold = 10\n", " self.min_area = 100\n", " \n", " # 调整速率系数\n", " self.THRESH_STEP = 2 # 阈值调整步长\n", " self.AREA_STEP = 5 # 面积调整步长\n", " self.TARGET_FPS = 15 # 目标处理速度(用于CPU优化)\n", "\n", " def adjust_by_fps(self, actual_fps):\n", " \"\"\"根据实际帧率动态调整参数\"\"\"\n", " if actual_fps < self.TARGET_FPS * 0.8:\n", " # 当帧率过低时激进调节参数\n", " self.THRESH_STEP = max(3, self.THRESH_STEP + 1)\n", " self.min_area = min(300, self.min_area + 10)\n", " elif actual_fps > self.TARGET_FPS * 1.2:\n", " # 当帧率过高时放松限制\n", " self.THRESH_STEP = max(1, self.THRESH_STEP - 1)\n", " \n", " def update(self, diff_frame, detected_areas):\n", " \"\"\"根据当前帧数据动态调整参数\"\"\"\n", " # 计算当前帧的运动强度\n", " curr_diff = cv2.mean(diff_frame)[0]\n", " self.diff_history.append(curr_diff)\n", " \n", " # 计算当前检测区域面积\n", " curr_area = sum([(x2-x1)*(y2-y1) for (x1,y1,x2,y2) in detected_areas])\n", " self.area_history.append(curr_area)\n", " \n", " # 计算平均运动强度(最近N帧的指数加权平均值)\n", " avg_diff = sum(self.diff_history) / len(self.diff_history) if self.diff_history else 0\n", " \n", " # 自动调整阈值(运动强度越低,阈值越高)\n", " if avg_diff < 10: # 低运动量阶段\n", " self.threshold = min(15, self.threshold + self.THRESH_STEP)\n", " elif avg_diff > 30: # 高运动量阶段\n", " self.threshold = max(15, self.threshold - self.THRESH_STEP*2)\n", " else: # 正常调整\n", " if len(detected_areas) > 5:\n", " self.threshold = max(10, self.threshold - self.THRESH_STEP)\n", " elif not detected_areas:\n", " self.threshold = min(50, self.threshold + self.THRESH_STEP)\n", " \n", " # 根据检测区域面积自动调整最小面积\n", " avg_area = sum(self.area_history) / len(self.area_history) if self.area_history else 0\n", " if avg_area < 500: # 小范围变化\n", " self.min_area = min(200, self.min_area + self.AREA_STEP)\n", " else: # 大范围变化\n", " self.min_area = max(50, self.min_area - self.AREA_STEP)" ] }, { "cell_type": "code", "execution_count": 5, "id": "857f6a30", "metadata": {}, "outputs": [], "source": [ "class FrameDiffProcessor:\n", " def __init__(self, ws_client: Optional[WebSocketClient] = None):\n", " self.prev_frame = None\n", " self.adjuster = DynamicAdjuster()\n", " self.kernel_size = (21, 21)\n", " self.last_diff = None\n", " self.ws_client = ws_client\n", "\n", " def _preprocess(self, frame):\n", " gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n", " return cv2.GaussianBlur(gray, self.kernel_size, 0)\n", "\n", " def process(self, frame):\n", " processed = self._preprocess(frame)\n", " if self.prev_frame is None:\n", " self.prev_frame = processed\n", " return [], self.adjuster.threshold, self.adjuster.min_area, 0\n", "\n", " # 计算原始差异\n", " raw_diff = cv2.absdiff(self.prev_frame, processed)\n", " self.last_diff = raw_diff.copy()\n", "\n", " # 应用当前阈值\n", " _, thresh = cv2.threshold(\n", " raw_diff, self.adjuster.threshold, 255, cv2.THRESH_BINARY\n", " )\n", "\n", " # 形态学优化\n", " kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))\n", " thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)\n", "\n", " # 查找轮廓并过滤\n", " contours, _ = cv2.findContours(\n", " thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE\n", " )\n", " rects = []\n", " for cnt in contours:\n", " area = cv2.contourArea(cnt)\n", " if area > self.adjuster.min_area:\n", " x, y, w, h = cv2.boundingRect(cnt)\n", " rects.append((x, y, x + w, y + h))\n", "\n", " if rects:\n", " payload = {\n", " \"type\": \"motion_detection\",\n", " \"timestamp\": int(time.time()*1000),\n", " \"regions\": [],\n", " \"stats\": {\n", " \"threshold\": self.adjuster.threshold,\n", " \"min_area\": self.adjuster.min_area,\n", " \"fps\": self.adjuster.TARGET_FPS,\n", " },\n", " }\n", " for (x1, y1, x2, y2) in rects:\n", " roi = frame[y1:y2, x1:x2]\n", " # 将图像转换为base64编码\n", " _, buffer = cv2.imencode('.webp', roi, [cv2.IMWRITE_WEBP_QUALITY, 80])\n", " payload[\"regions\"].append({\n", " \"x\": x1,\n", " \"y\": y1,\n", " \"width\": x2 - x1,\n", " \"height\": y2 - y1,\n", " \"image\": buffer.tobytes().hex() # 转换为十六进制字符串\n", " })\n", " \n", " # 通过WebSocket发送\n", " self.ws_client.send(payload)\n", " # 动态参数调整\n", " self.adjuster.update(raw_diff, rects)\n", "\n", " self.prev_frame = processed\n", " return (\n", " rects,\n", " self.adjuster.threshold,\n", " self.adjuster.min_area,\n", " self.adjuster.TARGET_FPS,\n", " )\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "108febd8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WebSocket 连接成功\n" ] }, { "ename": "KeyboardInterrupt", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[6], line 14\u001b[0m\n\u001b[0;32m 11\u001b[0m sct_img \u001b[38;5;241m=\u001b[39m sct\u001b[38;5;241m.\u001b[39mgrab(monitor)\n\u001b[0;32m 12\u001b[0m frame \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray(sct_img)\n\u001b[1;32m---> 14\u001b[0m rects, curr_thresh, curr_area, fps \u001b[38;5;241m=\u001b[39m processor\u001b[38;5;241m.\u001b[39mprocess(frame)\n\u001b[0;32m 16\u001b[0m \u001b[38;5;66;03m# 计算实际FPS\u001b[39;00m\n\u001b[0;32m 17\u001b[0m curr_time \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime()\n", "Cell \u001b[1;32mIn[5], line 14\u001b[0m, in \u001b[0;36mFrameDiffProcessor.process\u001b[1;34m(self, frame)\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mprocess\u001b[39m(\u001b[38;5;28mself\u001b[39m, frame):\n\u001b[1;32m---> 14\u001b[0m processed \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_preprocess(frame)\n\u001b[0;32m 15\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprev_frame \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m 16\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprev_frame \u001b[38;5;241m=\u001b[39m processed\n", "Cell \u001b[1;32mIn[5], line 10\u001b[0m, in \u001b[0;36mFrameDiffProcessor._preprocess\u001b[1;34m(self, frame)\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_preprocess\u001b[39m(\u001b[38;5;28mself\u001b[39m, frame):\n\u001b[1;32m---> 10\u001b[0m gray \u001b[38;5;241m=\u001b[39m cv2\u001b[38;5;241m.\u001b[39mcvtColor(frame, cv2\u001b[38;5;241m.\u001b[39mCOLOR_BGR2GRAY)\n\u001b[0;32m 11\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m cv2\u001b[38;5;241m.\u001b[39mGaussianBlur(gray, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mkernel_size, \u001b[38;5;241m0\u001b[39m)\n", "\u001b[1;31mKeyboardInterrupt\u001b[0m: " ] }, { "name": "stdout", "output_type": "stream", "text": [ "WebSocket 错误: Connection to remote host was lost.\n", "WebSocket 关闭: None - None\n", "3秒后尝试重连...\n", "WebSocket 连接成功\n" ] } ], "source": [ "# 初始化捕获\n", "sct = mss()\n", "monitor = sct.monitors[1]\n", "ws_client = WebSocketClient(\"ws://43.248.184.71:48905?mac=server\")\n", "ws_client.connect() \n", "processor = FrameDiffProcessor(ws_client)\n", "\n", "cv2.namedWindow(\"Adaptive Detection\")\n", "prev_time = time.time()\n", "while True:\n", " sct_img = sct.grab(monitor)\n", " frame = np.array(sct_img)\n", "\n", " rects, curr_thresh, curr_area, fps = processor.process(frame)\n", "\n", " # 计算实际FPS\n", " curr_time = time.time()\n", " actual_fps = 1 / (curr_time - prev_time)\n", " prev_time = curr_time\n", "\n", " # 调节参数\n", " processor.adjuster.adjust_by_fps(actual_fps)\n", " # 可视化\n", " display = frame.copy()\n", " for (x1, y1, x2, y2) in rects:\n", " cv2.rectangle(display, (x1, y1), (x2, y2), (0, 255, 0), 2)\n", "\n", " # 显示调整信息\n", " status = f\"Thresh: {curr_thresh} | MinArea: {curr_area}| FPS:{ fps}\"\n", " cv2.putText(display, status, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)\n", "\n", " # 显示差异图(可选)\n", " # if processor.last_diff is not None:\n", " # diff_display = cv2.normalize(processor.last_diff, None, 0, 255, cv2.NORM_MINMAX)\n", " # cv2.imshow(\"Difference\", diff_display.astype(np.uint8))\n", "\n", " cv2.imshow(\"Adaptive Detection\", display)\n", "\n", " if cv2.waitKey(25) & 0xFF == 27:\n", " break\n", "\n", "cv2.destroyAllWindows()\n" ] } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.4" } }, "nbformat": 4, "nbformat_minor": 5 }