The AirCanvas project allows you to draw on your computer screen in real-time using hand gestures in front of a webcam. The application uses computer vision to track finger movements and overlay the drawing onto the video stream.
AirCanva/
├── main.py # Main application script (video capture, tracking, drawing)
├── requirements.txt # List of external Python dependencies
├── hand_landmarker.task # MediaPipe model for hand landmark recognition
├── .gitignore # Git version control exclusions
└── README.md # Project documentation
main.py— contains the main processing loop: capturing frames from the webcam, recognizing hands usingcvzone, handling finger gestures, drawing lines on a graphical canvas, and merging the canvas with the live video stream.requirements.txt— required libraries (OpenCV, cvzone, MediaPipe, NumPy, etc.).hand_landmarker.task— pre-trained MediaPipe Tasks API model for detecting and localizing 21 hand landmarks.
Main libraries used in the project:
opencv-python/opencv-contrib-python(cv2):
- Video stream capture from the webcam (
cv2.VideoCapture). - Frame processing (mirroring
cv2.flip, layer blending, color space conversions). - Drawing graphical primitives (circles
cv2.circle, linescv2.line, textcv2.putText). - Displaying the final window (
cv2.imshow).
cvzone(HandTrackingModule.HandDetector):
- Convenient high-level wrapper over MediaPipe.
- Detects hands in a frame (
findHands) and determines finger states: raised/lowered (fingersUp).
mediapipe:
- Google's framework for real-time computer vision ML models. Powers hand detection in
cvzone.
numpy:
- Pixel matrix manipulations.
- Initializing a clean canvas (
np.zeros_like(frame)) where drawing takes place.
Let's break down the program's logic step by step:
cap = cv2.VideoCapture(0)
cap.set(3, 1280) # Frame width
cap.set(4, 720) # Frame height
detector = HandDetector(detectionCon=0.8, maxHands=1)- Connects to the first available webcam (
0) and sets the resolution to1280x720. - Creates a
HandDetectorobject with an 80% recognition confidence threshold (detectionCon=0.8) and a limit of 1 hand per frame.
canvas = None
xp, yp = 0, 0 # Previous coordinates of the fingertip
brush_thickness = 15 # Brush thickness
eraser_thickness = 50 # Eraser thickness
draw_color = (0, 255, 0) # Drawing color BGR (green)while cap.isOpened():
success, frame = cap.read()
if not success:
break
frame = cv2.flip(frame, 1) # Mirror the frame for convenience
if canvas is None:
canvas = np.zeros_like(frame) # Create a blank black canvas matching frame sizeUsing detector.findHands, we get a list of detected hands and landmarks (lmList).
- Landmark 8 — tip of the index finger (
x1, y1). - Landmark 12 — tip of the middle finger (
x2, y2). fingers = detector.fingersUp(hand)returns an array of 5 elements[thumb, index, middle, ring, pinky](1- raised,0- lowered).
if fingers[1] and fingers[2]:
cv2.circle(frame, (x1, y1), eraser_thickness // 2, (0, 0, 255), -1)
if xp == 0 and yp == 0:
xp, yp = x1, y1
cv2.line(canvas, (xp, yp), (x1, y1), (0, 0, 0), eraser_thickness)
xp, yp = x1, y1- Gesture: index and middle fingers are raised.
- A red eraser pointer circle is drawn on the frame.
- A black line
(0, 0, 0)oferaser_thicknessis drawn on thecanvas, "erasing" what was drawn.
elif fingers[1] and not fingers[2]:
cv2.circle(frame, (x1, y1), brush_thickness // 2, draw_color, -1)
if xp == 0 and yp == 0:
xp, yp = x1, y1
cv2.line(canvas, (xp, yp), (x1, y1), draw_color, brush_thickness)
xp, yp = x1, y1- Gesture: only the index finger is raised (middle finger is lowered).
- Draws a line of the selected
draw_colorfrom the previous point(xp, yp)to the current point(x1, y1).
elif all(fingers[1:]):
canvas = np.zeros_like(frame)
cv2.putText(frame, "Canvas Cleared", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3)
xp, yp = 0, 0- Gesture: all 4 fingers are raised (index, middle, ring, pinky).
- The canvas is completely cleared (filled with zeros).
img_gray = cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY)
_, img_inv = cv2.threshold(img_gray, 50, 255, cv2.THRESH_BINARY_INV)
img_inv = cv2.cvtColor(img_inv, cv2.COLOR_GRAY2BGR)
frame = cv2.bitwise_and(frame, img_inv)
frame = cv2.bitwise_or(frame, canvas)- Convert
canvasto grayscale. - Create an inverted binary mask
img_inv: where there is a drawing on the canvas, the mask becomes black (0). - Use
bitwise_andto "cut out" the area for the drawing on the original webcam frame. - Use
bitwise_orto overlay the colored pixels of thecanvasonto the prepared frame.
- Change Brush Color:
In
main.py, modify thedraw_colortuple in BGR format (Blue, Green, Red):
draw_color = (255, 0, 0) # Blue
draw_color = (0, 0, 255) # Red
draw_color = (0, 255, 255) # Yellow- Change Brush or Eraser Thickness:
brush_thickness = 20 # Make brush thicker
eraser_thickness = 80 # Make eraser larger- Change Webcam Resolution:
cap.set(3, 1920) # Width
cap.set(4, 1080) # Height- Keybindings:
The application exits when pressing the
qkey. To change the key, replace'q'with another letter:
if cv2.waitKey(1) & 0xFF == ord('q'):
break- 🎨 Interactive On-Screen Color Palette:
- Draw colored rectangles at the top of the screen (red, green, blue, eraser).
- Automatically switch
draw_colorwhen hovering the index + middle finger over a rectangle area.
- 📈 Line Smoothing (Moving Average / Bezier):
- Smooth out fast hand movements using exponential smoothing or a Kalman filter for coordinates
(x1, y1)to make drawing smoother.
- ↩️ Undo / Redo Stack:
- Store canvas states in a
dequehistory list to implement undoing the last stroke via a gesture or hotkey (e.g.,Ctrl+Z).
- 💾 Save Drawing:
- Add the ability to save the current canvas to a file (
.png/.jpg) using a keypress (e.g.,s) withcv2.imwrite('drawing.png', canvas).
- 🎛️ Dynamic Brush Sizing by Gesture:
- Read the distance between the thumb and index finger to dynamically change brush thickness right while drawing.
- 🏗️ OOP Code Structure:
- Extract tracking, gesture processing, and canvas management logic into separate classes (
HandTracker,CanvasManager,UIOverlay).
Проект AirCanvas позволяет рисовать на экране компьютера в режиме реального времени с помощью жестов рук перед веб-камерой. Приложение использует компьютерное зрение для отслеживания движения пальцев и наложения рисунка на видеопоток.
AirCanva/
├── main.py # Основной скрипт приложения (захват видео, трекинг, рисование)
├── requirements.txt # Список внешних зависимостей Python
├── hand_landmarker.task # Модель MediaPipe для распознавания ключевых точек руки
├── .gitignore # Исключения для системы контроля версий Git
└── README.md # Документация проекта
-
main.py— содержит весь основной цикл обработки: захват кадров с веб-камеры, распознавание рук с помощьюcvzone, обработку жестов пальцев, отрисовку линий на графическом холсте и слияние холста с реальным видеопотоком. -
requirements.txt— библиотеки, необходимые для работы (OpenCV, cvzone, MediaPipe, NumPy и др.). -
hand_landmarker.task— предобученная модель MediaPipe Tasks API для детекции и локализации 21 ключевой точки на кисти руки.
Основные библиотеки, используемые в проекте:
opencv-python/opencv-contrib-python(cv2):
-
Захват видеопотока с веб-камеры (
cv2.VideoCapture). -
Обработка кадра (зеркальное отражение
cv2.flip, смешивание слоев, цветовые преобразования). -
Отрисовка графических примитивов (круги
cv2.circle, линииcv2.line, текстcv2.putText). -
Отображение итогового окна (
cv2.imshow).
cvzone(HandTrackingModule.HandDetector):
-
Удобная высокоуровневая обертка над MediaPipe.
-
Находит кисть руки на кадре (
findHands) и определяет статус пальцев: поднят/опущен (fingersUp).
mediapipe:
- Фреймворк от Google для работы с ML-моделями компьютерного зрения в реальном времени. На его основе работает детекция рук в
cvzone.
numpy:
-
Манипуляции с матрицами пикселей.
-
Инициализация чистого холста (
np.zeros_like(frame)), на котором происходит рисование.
Рассмотрим логику работы программы по шагам:
cap = cv2.VideoCapture(0)
cap.set(3, 1280) # Ширина кадра
cap.set(4, 720) # Высота кадра
detector = HandDetector(detectionCon=0.8, maxHands=1)-
Подключается первая доступная веб-камера (
0) и задается разрешение1280x720. -
Создается объект
HandDetectorс порогом уверенности распознавания 80% (detectionCon=0.8) и ограничением в 1 руку на кадр.
canvas = None
xp, yp = 0, 0 # Предыдущие координаты кончика пальца
brush_thickness = 15 # Толщина кисти
eraser_thickness = 50 # Толщина ластика
draw_color = (0, 255, 0) # Цвет рисования BGR (зеленый)while cap.isOpened():
success, frame = cap.read()
if not success:
break
frame = cv2.flip(frame, 1) # Зеркальный разворот кадра для удобства
if canvas is None:
canvas = np.zeros_like(frame) # Создаем пустой черный холст под размер кадраС помощью detector.findHands получаем список найденных рук и ключевых точек (lmList).
-
Точка 8 — кончик указательного пальца (
x1, y1). -
Точка 12 — кончик среднего пальца (
x2, y2). -
fingers = detector.fingersUp(hand)возвращает массив из 5 элементов[большой, указательный, средний, безымянный, мизинец](1- поднят,0- опущен).
if fingers[1] and fingers[2]:
cv2.circle(frame, (x1, y1), eraser_thickness // 2, (0, 0, 255), -1)
if xp == 0 and yp == 0:
xp, yp = x1, y1
cv2.line(canvas, (xp, yp), (x1, y1), (0, 0, 0), eraser_thickness)
xp, yp = x1, y1-
Жест: поднят указательный и средний пальцы.
-
На кадре рисуется красный круг-указатель ластика.
-
На холсте
canvasпроводится черная линия(0, 0, 0)толщинойeraser_thickness, которая "стирает" нарисованное.
elif fingers[1] and not fingers[2]:
cv2.circle(frame, (x1, y1), brush_thickness // 2, draw_color, -1)
if xp == 0 and yp == 0:
xp, yp = x1, y1
cv2.line(canvas, (xp, yp), (x1, y1), draw_color, brush_thickness)
xp, yp = x1, y1-
Жест: поднят только указательный палец (средний опущен).
-
Проводится линия выбранного цвета
draw_colorот предыдущей точки(xp, yp)до текущей(x1, y1).
elif all(fingers[1:]):
canvas = np.zeros_like(frame)
cv2.putText(frame, "Canvas Cleared", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3)
xp, yp = 0, 0-
Жест: подняты все 4 пальца (указательный, средний, безымянный, мизинец).
-
Холст полностью очищается (заполняется нулями).
img_gray = cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY)
_, img_inv = cv2.threshold(img_gray, 50, 255, cv2.THRESH_BINARY_INV)
img_inv = cv2.cvtColor(img_inv, cv2.COLOR_GRAY2BGR)
frame = cv2.bitwise_and(frame, img_inv)
frame = cv2.bitwise_or(frame, canvas)-
Переводим
canvasв градации серого. -
Создаем инвертированную бинарную маску
img_inv: там, где на холсте есть рисунок, маска становится черной (0). -
С помощью
bitwise_and"вырезаем" область под рисунок на исходном кадре с веб-камеры. -
С помощью
bitwise_orнакладываем цветные пикселиcanvasна подготовленный кадр.
- Изменить цвет кисти:
В main.py измените кортеж draw_color в формате BGR (Blue, Green, Red):
draw_color = (255, 0, 0) # Синий
draw_color = (0, 0, 255) # Красный
draw_color = (0, 255, 255) # Желтый- Изменить толщину кисти или ластика:
brush_thickness = 20 # Сделать кисть толще
eraser_thickness = 80 # Сделать ластик больше- Изменить разрешение веб-камеры:
cap.set(3, 1920) # Ширина
cap.set(4, 1080) # Высота- Настройка клавиш:
Завершение работы происходит по нажатию клавиши q. Чтобы сменить клавишу, замените 'q' на другую букву в строке:
if cv2.waitKey(1) & 0xFF == ord('q'):
break- 🎨 Интерактивная палитра цветов на экране:
-
Отрисовать в верхней части экрана цветные прямоугольники (красный, зеленый, синий, стиратель).
-
При наведении указательного+среднего пальца в область прямоугольника автоматически переключать
draw_color.
- 📈 Сглаживание линий (Moving Average / Bezier):
- Сейчас при быстром движении руки линии могут получаться угловатыми. Использование экспоненциального сглаживания или фильтра Калмана для координат
(x1, y1)сделает рисование более плавным.
- ↩️ Отмена и повтор (Undo / Redo Stack):
- Хранить историю состояний
canvasв списке (deque) для реализации отмены последнего штриха по жестку или горячей клавише (например,Ctrl+Z).
- 💾 Сохранение рисунка:
- Добавить возможность сохранения текущего холста в файл (
.png/.jpg) по нажатию клавиши (например,s) с помощьюcv2.imwrite('drawing.png', canvas).
- 🎛️ Изменение толщины кисти жестом:
- Считывать расстояние между большим и указательным пальцем для динамического изменения толщины кисти прямо во время рисования.
- 🏗️ ООП-структура кода:
- Вынести логику трекинга, обработки жестов и работы с холстом в отдельные классы (
HandTracker,CanvasManager,UIOverlay).