365 lines
15 KiB
Plaintext
365 lines
15 KiB
Plaintext
{
|
|
"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
|
|
}
|