{
 "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
}
