Ở Buổi 9, chúng ta truyền toàn bộ bức ảnh qua mạng (tốn băng thông). Trong kiến trúc IoT / Robot thực tế, người ta áp dụng mô hình Edge Computing (Tính toán biên):
Edge Device (Camera AI Client): Xử lý hình ảnh và MediaPipe ngay tại chỗ, chỉ trích xuất các con số tọa độ then chốt rồi đóng gói thành định dạng JSON siêu nhẹ (chỉ vài chục bytes).
Controller Server (Máy tính điều khiển / Robot): Nhận gói tin JSON qua Socket để điều khiển con trỏ chuột máy tính (pyautogui) hoặc gửi lệnh ra mạch phần cứng.
[Camera + MediaPipe]
↓ (Tính toán tại máy khách)
Tọa độ ngón trỏ (x, y) & Trạng thái Búng/Chạm (Pinch)
↓
Đóng gói JSON: {"x": 320, "y": 240, "click": true} (~50 bytes)
↓ (Truyền Socket siêu tốc qua LAN/Wifi)
[Controller Server]
↓
Điều khiển chuột máy tính: pyautogui.moveTo(x, y) / click()
Trên máy đóng vai trò Server (máy sẽ bị điều khiển chuột), mở Terminal cài đặt thư viện pyautogui:
pip install pyautogui
Ta chia thành 2 file:
edge_camera_client.py: Bật camera, nhận diện cử chỉ tay, gửi gói tin JSON qua Socket.
mouse_controller_server.py: Lắng nghe Socket, nhận tọa độ và di chuyển chuột theo thời gian thực.
edge_camera_client.py (Bên xử lý AI & gửi tọa độ)Client nhận diện đầu ngón trỏ (Landmark 8) làm tọa độ chuột và khoảng cách giữa ngón trỏ với ngón cái (Landmark 4) để thực hiện thao tác Click chuột (Pinch Gesture).
import cv2
import socket
import json
import numpy as np
from mediapipe.python.solutions import hands as mp_hands
from mediapipe.python.solutions import drawing_utils as mp_draw
class EdgeCameraClient:
def __init__(self, server_ip='127.0.0.1', port=9000, cam_index=0):
self.server_ip = server_ip
self.port = port
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.cap = cv2.VideoCapture(cam_index)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# Khởi tạo MediaPipe Hands
self.hands = mp_hands.Hands(
max_num_hands=1,
min_detection_confidence=0.7,
min_tracking_confidence=0.7
)
def start(self):
if not self.cap.isOpened():
print("❌ Không thể mở camera.")
return
print(f"🔗 Đang kết nối tới Edge Controller Server ({self.server_ip}:{self.port})...")
try:
self.client_socket.connect((self.server_ip, self.port))
print("✅ Đã kết nối thành công! Bắt đầu gửi dữ liệu tọa độ...")
except Exception as e:
print(f"❌ Kết nối thất bại: {e}")
return
try:
while True:
ret, frame = self.cap.read()
if not ret:
break
frame = cv2.flip(frame, 1)
h, w, _ = frame.shape
# Xử lý MediaPipe
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
res = self.hands.process(rgb)
if res.multi_hand_landmarks:
hand = res.multi_hand_landmarks[0]
# Landmark 8: Đầu ngón trỏ
x_index = int(hand.landmark[8].x * w)
y_index = int(hand.landmark[8].y * h)
# Landmark 4: Đầu ngón cái
x_thumb = int(hand.landmark[4].x * w)
y_thumb = int(hand.landmark[4].y * h)
# Tính khoảng cách Euclidean giữa ngón trỏ và ngón cái
distance = np.hypot(x_index - x_thumb, y_index - y_thumb)
is_clicking = bool(distance < 35) # Chạm ngón thì tính là Click
# Vẽ trực quan lên camera
mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS)
color = (0, 0, 255) if is_clicking else (0, 255, 0)
cv2.circle(frame, (x_index, y_index), 10, color, cv2.FILLED)
# Đóng gói dữ liệu JSON
payload = {
"x": x_index,
"y": y_index,
"click": is_clicking,
"frame_w": w,
"frame_h": h
}
# Gửi qua socket kèm ký tự xuống dòng '\n' làm dấu phân tách
msg = json.dumps(payload) + "\n"
self.client_socket.sendall(msg.encode('utf-8'))
cv2.imshow("Edge AI Client (Camera View)", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except (ConnectionResetError, BrokenPipeError):
print("❌ Mất kết nối tới Server.")
finally:
self.cap.release()
self.client_socket.close()
cv2.destroyAllWindows()
print(" Đã tắt Edge AI Client.")
if __name__ == "__main__":
# Đổi IP sang máy Server nếu chạy trên 2 máy khác nhau trong mạng Wifi
client = EdgeCameraClient(server_ip='127.0.0.1', port=9000)
client.start()
mouse_controller_server.py (Bên nhận và điều khiển chuột)Server đọc gói tin JSON, ánh xạ tỷ lệ màn hình (Scale Mapping) và điều khiển con trỏ chuột.
import socket
import json
import pyautogui
# Tắt tính năng tự dừng khẩn cấp khi chuột chạm góc màn hình (để tránh văng lỗi ngoài ý muốn)
pyautogui.FAILSAFE = False
class MouseControllerServer:
def __init__(self, host='0.0.0.0', port=9000):
self.host = host
self.port = port
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Lấy độ phân giải thực của màn hình máy tính này
self.screen_w, self.screen_h = pyautogui.size()
print(f"🖥️ Màn hình Server: {self.screen_w}x{self.screen_h}")
# Trạng thái click trước đó để tránh nhấn đè liên tục
self.was_clicked = False
def start(self):
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(1)
print(f"📡 Server đang chờ Edge Device kết nối tại cổng {self.port}...")
conn, addr = self.server_socket.accept()
print(f"✅ Đã kết nối với Edge Client: {addr}")
buffer = ""
try:
while True:
data = conn.recv(1024).decode('utf-8')
if not data:
break
buffer += data
# Tách từng gói tin theo ký tự kết thúc '\n'
while "\n" in buffer:
line, buffer = buffer.split("\n", 1)
if line.strip():
payload = json.loads(line)
self.handle_command(payload)
except (ConnectionResetError, json.JSONDecodeError):
print("❌ Mất kết nối hoặc lỗi giải mã JSON.")
finally:
conn.close()
self.server_socket.close()
print(" Đã đóng Server.")
def handle_command(self, payload):
cam_x = payload["x"]
cam_y = payload["y"]
is_click = payload["click"]
cam_w = payload["frame_w"]
cam_h = payload["frame_h"]
# Ánh xạ tọa độ từ khung camera (640x480) sang màn hình PC thực tế
# Quy tắc tam xuất: screen_pos = (cam_pos / cam_size) * screen_size
target_x = int((cam_x / cam_w) * self.screen_w)
target_y = int((cam_y / cam_h) * self.screen_h)
# Di chuyển con trỏ chuột
pyautogui.moveTo(target_x, target_y)
# Kích hoạt Click chuột khi chạm ngón tay (chỉ kích hoạt 1 lần khi bắt đầu chạm)
if is_click and not self.was_clicked:
pyautogui.click()
print(f"🖱️ Click chuột tại: ({target_x}, {target_y})")
self.was_clicked = True
elif not is_click:
self.was_clicked = False
if __name__ == "__main__":
server = MouseControllerServer(host='0.0.0.0', port=9000)
server.start()
Mở Terminal 1: Chạy python mouse_controller_server.py.
Mở Terminal 2: Chạy python edge_camera_client.py.
Giơ ngón trỏ lên trước camera và di chuyển trong không trung: Con trỏ chuột trên màn hình máy tính sẽ di chuyển theo đúng hướng tay bé.
Chạm ngón cái và ngón trỏ lại với nhau (đốm sáng chuyển sang màu đỏ): Máy tính sẽ thực hiện một cú Click chuột trái.
Thử thách 1 (Làm mịn chuyển động - Smoothing Filter):
Tay người luôn có độ rung tự nhiên nhẹ, khiến con trỏ chuột hơi bị giật nhấp nháy.
Gợi ý cho bé: Làm sao dùng công thức trung bình cộng lũy tiến (Exponential Moving Average):
để con trỏ chuột lướt êm ái như bay?
Thử thách 2 (Khu vực thao tác giới hạn - Margin Box):
Vì camera góc rộng, tay phải vung rất xa mới chạm mép màn hình.
Nhiệm vụ: Vẽ một hình chữ nhật nhỏ ở trung tâm khung camera và quy định: di chuyển tay trong ô đó đã đủ phủ kín toàn bộ màn hình máy tính.