Files
python----/remoteDesk.py
2025-10-20 12:32:18 +08:00

283 lines
8.9 KiB
Python

# %%
import time
import cv2
import numpy as np
from mss import mss
from collections import deque
import pyautogui
# %%
from typing import Callable, Optional
# %%
import json
import threading
import websocket
from queue import Queue
class WebSocketClient:
def __init__(self, server_url):
self.server_url = server_url
self.ws = None
self.connected = False
self.message_queue = Queue()
self.lock = threading.Lock()
self.reconnect_delay = 3 # 重连延迟(秒)
self.running = False
def on_open(self, ws):
"""连接成功回调"""
print("WebSocket 连接成功")
self.connected = True
def on_message(self, ws, message):
"""接收消息回调"""
print(f"收到前端消息: {message}")
data = json.loads(message)
if data.get("type") == "click":
x, y = data["position"].values()
pyautogui.click(x, y)
def on_error(self, ws, error):
"""错误回调"""
print(f"WebSocket 错误: {error}")
self.connected = False
def on_close(self, ws, close_status_code, close_msg):
"""关闭回调"""
print(f"WebSocket 关闭: {close_status_code} - {close_msg}")
self.connected = False
if self.running:
self.reconnect()
def reconnect(self):
"""断线重连"""
print(f"{self.reconnect_delay}秒后尝试重连...")
time.sleep(self.reconnect_delay)
self.connect()
def connect(self):
"""连接WebSocket服务器"""
self.running = True
self.ws = websocket.WebSocketApp(
self.server_url,
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close,
)
# 在独立线程中运行WebSocket
threading.Thread(target=self.ws.run_forever, daemon=True).start()
def send(self, data):
"""发送消息(线程安全)"""
with self.lock:
if self.connected and self.ws:
try:
self.ws.send("xtleto|" + json.dumps(data))
return True
except Exception as e:
print(f"消息发送失败: {e}")
return False
return False
def stop(self):
"""停止WebSocket连接"""
self.running = False
if self.ws:
self.ws.close()
# %%
class DynamicAdjuster:
def __init__(self):
# 参数跟踪窗口(保留最近20帧数据)
fps = 30
self.diff_history = deque(maxlen=fps)
self.area_history = deque(maxlen=fps)
# 初始参数值
self.threshold = 10
self.min_area = 100
# 调整速率系数
self.THRESH_STEP = 2 # 阈值调整步长
self.AREA_STEP = 5 # 面积调整步长
self.TARGET_FPS = fps # 目标处理速度(用于CPU优化)
def adjust_by_fps(self, actual_fps):
"""根据实际帧率动态调整参数"""
if actual_fps < self.TARGET_FPS * 0.8:
# 当帧率过低时激进调节参数
self.THRESH_STEP = max(3, self.THRESH_STEP + 1)
self.min_area = min(300, self.min_area + 10)
elif actual_fps > self.TARGET_FPS * 1.2:
# 当帧率过高时放松限制
self.THRESH_STEP = max(1, self.THRESH_STEP - 1)
def update(self, diff_frame, detected_areas):
"""根据当前帧数据动态调整参数"""
# 计算当前帧的运动强度
curr_diff = cv2.mean(diff_frame)[0]
self.diff_history.append(curr_diff)
# 计算当前检测区域面积
curr_area = sum([(x2 - x1) * (y2 - y1) for (x1, y1, x2, y2) in detected_areas])
self.area_history.append(curr_area)
# 计算平均运动强度(最近N帧的指数加权平均值)
avg_diff = (
sum(self.diff_history) / len(self.diff_history) if self.diff_history else 0
)
# 自动调整阈值(运动强度越低,阈值越高)
if avg_diff < 10: # 低运动量阶段
self.threshold = min(20, self.threshold + self.THRESH_STEP)
elif avg_diff > 30: # 高运动量阶段
self.threshold = max(15, self.threshold - self.THRESH_STEP * 2)
else: # 正常调整
if len(detected_areas) > 5:
self.threshold = max(10, self.threshold - self.THRESH_STEP)
elif not detected_areas:
self.threshold = min(50, self.threshold + self.THRESH_STEP)
# 根据检测区域面积自动调整最小面积
avg_area = (
sum(self.area_history) / len(self.area_history) if self.area_history else 0
)
if avg_area < 500: # 小范围变化
self.min_area = min(200, self.min_area + self.AREA_STEP)
else: # 大范围变化
self.min_area = max(50, self.min_area - self.AREA_STEP)
# %%
class FrameDiffProcessor:
def __init__(self, ws_client: Optional[WebSocketClient] = None):
self.prev_frame = None
self.adjuster = DynamicAdjuster()
self.kernel_size = (21, 21)
self.last_diff = None
self.ws_client = ws_client
def _preprocess(self, frame):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return cv2.GaussianBlur(gray, self.kernel_size, 0)
def process(self, frame):
processed = self._preprocess(frame)
if self.prev_frame is None:
self.prev_frame = processed
return [], self.adjuster.threshold, self.adjuster.min_area, 0
# 计算原始差异
raw_diff = cv2.absdiff(self.prev_frame, processed)
self.last_diff = raw_diff.copy()
# 应用当前阈值
_, thresh = cv2.threshold(
raw_diff, self.adjuster.threshold, 255, cv2.THRESH_BINARY
)
# 形态学优化
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 查找轮廓并过滤
contours, _ = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
rects = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area > self.adjuster.min_area:
x, y, w, h = cv2.boundingRect(cnt)
rects.append((x, y, x + w, y + h))
if rects:
payload = {
"type": "motion_detection",
"timestamp": int(time.time() * 1000),
"regions": [],
"stats": {
"threshold": self.adjuster.threshold,
"min_area": self.adjuster.min_area,
"fps": self.adjuster.TARGET_FPS,
},
}
for x1, y1, x2, y2 in rects:
roi = frame[y1:y2, x1:x2]
# 将图像转换为base64编码
_, buffer = cv2.imencode(".webp", roi, [cv2.IMWRITE_WEBP_QUALITY, 80])
payload["regions"].append(
{
"x": x1,
"y": y1,
"width": x2 - x1,
"height": y2 - y1,
"image": buffer.tobytes().hex(), # 转换为十六进制字符串
}
)
# 通过WebSocket发送
self.ws_client.send(payload)
# 动态参数调整
self.adjuster.update(raw_diff, rects)
self.prev_frame = processed
return (
rects,
self.adjuster.threshold,
self.adjuster.min_area,
self.adjuster.TARGET_FPS,
)
# %%
# 初始化捕获
sct = mss()
monitor = sct.monitors[1]
ws_client = WebSocketClient("ws://43.248.184.71:48905?mac=server")
ws_client.connect()
processor = FrameDiffProcessor(ws_client)
cv2.namedWindow("Adaptive Detection")
prev_time = time.time()
while True:
sct_img = sct.grab(monitor)
frame = np.array(sct_img)
rects, curr_thresh, curr_area, fps = processor.process(frame)
# 计算实际FPS
curr_time = time.time()
actual_fps = 1 / (curr_time - prev_time)
prev_time = curr_time
# 调节参数
processor.adjuster.adjust_by_fps(actual_fps)
# 可视化
display = frame.copy()
for x1, y1, x2, y2 in rects:
cv2.rectangle(display, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 显示调整信息
status = f"Thresh: {curr_thresh} | MinArea: {curr_area}| FPS:{ fps}"
cv2.putText(
display, status, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2
)
# 显示差异图(可选)
# if processor.last_diff is not None:
# diff_display = cv2.normalize(processor.last_diff, None, 0, 255, cv2.NORM_MINMAX)
# cv2.imshow("Difference", diff_display.astype(np.uint8))
cv2.imshow("Adaptive Detection", display)
if cv2.waitKey(25) & 0xFF == 27:
break
cv2.destroyAllWindows()
ws_client.stop()