Skip to content

Commit 4ba9278

Browse files
committed
init
0 parents  commit 4ba9278

File tree

1,642 files changed

+203
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,642 files changed

+203
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Banyar Oo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.MD

Lines changed: 14 additions & 0 deletions

capture.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import cv2
2+
import mediapipe as mp
3+
import numpy as np
4+
import uuid
5+
import os
6+
7+
resized = None
8+
crop_size = (83, 84)
9+
10+
def restart_canvas():
11+
# black canvas, contour_canvas
12+
# 3 is for channel
13+
return np.zeros((480, 640, 3), dtype=np.uint8), np.zeros((480, 640), dtype=np.uint8)
14+
15+
vc = cv2.VideoCapture(index=0)
16+
17+
canvas, contour_canvas = restart_canvas()
18+
19+
# distance threshold between thumb tip and finger tip
20+
threshold = 50
21+
22+
# bursh size of ink
23+
brush_size = 5
24+
25+
# Stores previous finger position
26+
prev_x, prev_y = None, None
27+
28+
# draw mode
29+
brush_mode = True
30+
31+
# Solutions API
32+
33+
# Inititalie MediaPipe FaceMesh
34+
mp_face_mesh = mp.solutions.face_mesh
35+
36+
# Initialize MediaPipe Hands
37+
mp_hands = mp.solutions.hands
38+
39+
# Instantiate the Hands class
40+
hands = mp_hands.Hands(
41+
static_image_mode=False, # False for video streams (better performance)
42+
max_num_hands=1, # Max number of hands to detect
43+
min_detection_confidence=0.5, # Confidence threshold to start tracking
44+
min_tracking_confidence=0.5 # Confidence threshold to continue tracking
45+
)
46+
47+
# For drawing landmarks
48+
mp_drawing = mp.solutions.drawing_utils
49+
50+
while vc.isOpened():
51+
# read each frame
52+
success, frame = vc.read()
53+
54+
# flip for mirror effect
55+
frame = cv2.flip(src=frame, flipCode=1)
56+
57+
# media pipe requires RGB
58+
# works with RGB, but renders BGR
59+
rgb_frame = cv2.cvtColor(src=frame, code=cv2.COLOR_BGR2RGB)
60+
61+
# process with model
62+
detect = hands.process(rgb_frame)
63+
64+
# frame height, width and number of channels (3)
65+
h, w, ch = frame.shape
66+
67+
# if detected
68+
if detect.multi_hand_landmarks:
69+
for hand_landmarks in detect.multi_hand_landmarks:
70+
mp_drawing.draw_landmarks(
71+
frame,
72+
hand_landmarks,
73+
mp_hands.HAND_CONNECTIONS,
74+
mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2), # Landmark color
75+
mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2) # Connection color
76+
)
77+
78+
# landmark node (x, y) is standarized to be [0, 1],
79+
# so to get multiply it with frame values (finger.x * frame.x)
80+
finger_tip = hand_landmarks.landmark[8]
81+
thumb_tip = hand_landmarks.landmark[12]
82+
83+
(finger_x, finger_y) = (int(finger_tip.x * w), int(finger_tip.y * h))
84+
(thumb_x, thumb_y) = (int(thumb_tip.x * w), int(thumb_tip.y * h))
85+
86+
# Compute Euclidean distance between index and thumb tips
87+
distance = np.linalg.norm(np.array([finger_x, finger_y]) - np.array([thumb_x, thumb_y]))
88+
89+
if distance > threshold:
90+
91+
cv2.circle(frame, (finger_x, finger_y), 5, (0, 255, 255), -1) # Yellow preview circle
92+
93+
# Draw on canvas if finger is moving
94+
if prev_x and prev_y:
95+
cv2.line(canvas, (prev_x, prev_y), (finger_x, finger_y), (255, 255, 255), brush_size)
96+
97+
prev_x, prev_y = finger_x, finger_y
98+
99+
else:
100+
# red circle to show that currently not drawing
101+
cv2.circle(frame, (finger_x, finger_y), 5, (0, 0, 255), -1)
102+
103+
if brush_mode:
104+
prev_x, prev_y = None, None
105+
106+
# draw contours
107+
contours, _ = cv2.findContours(cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
108+
cv2.drawContours(contour_canvas, contours, -1, 255)
109+
110+
if contours:
111+
x_min = min(cv2.boundingRect(c)[0] for c in contours)
112+
y_min = min(cv2.boundingRect(c)[1] for c in contours)
113+
x_max = max(cv2.boundingRect(c)[0] + cv2.boundingRect(c)[2] for c in contours)
114+
y_max = max(cv2.boundingRect(c)[1] + cv2.boundingRect(c)[3] for c in contours)
115+
116+
# crop according to contour
117+
cropped = canvas.copy()[y_min:y_max, x_min:x_max]
118+
119+
_, contour_canvas = restart_canvas()
120+
121+
# cv2.imshow('cropped', cropped)
122+
123+
# invert colors
124+
# cropped = cv2.bitwise_not(cropped)
125+
126+
# resize to fit the desired size
127+
resized = cv2.resize(cropped, crop_size)
128+
129+
# if brush_mode:
130+
# cropped = cv2.GaussianBlur(cropped, (15, 15), 0)
131+
132+
# cv2.imshow('resized', resized)
133+
134+
# overlay two images
135+
frame = cv2.addWeighted(frame, 0.6, canvas, 0.4, 0)
136+
137+
cv2.putText(frame, f'threshold:{threshold}', (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 1, cv2.LINE_AA)
138+
139+
if brush_mode:
140+
mode = 'brush'
141+
else:
142+
mode = 'line'
143+
cv2.putText(frame, f'Mode:{mode}', (10,60), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 1, cv2.LINE_AA)
144+
145+
cv2.imshow('frame', frame)
146+
# cv2.imshow('canvas', canvas)
147+
# cv2.imshow('contour canvas', contour_canvas)
148+
149+
key = cv2.waitKey(1)
150+
if key == 27: # esc
151+
break
152+
elif key == ord('m'): # m - 109
153+
threshold += 1
154+
elif key == ord('n'): # n - 110
155+
threshold -= 1
156+
elif key == ord('d'):
157+
canvas, contour_canvas = restart_canvas()
158+
elif key == ord('p'):
159+
brush_mode = not brush_mode
160+
elif key == ord('c'):
161+
# Generate a UUID and convert it to a string for use as a filename
162+
if 'saved' not in os.listdir('./'):
163+
os.makedirs('./saved', exist_ok=True)
164+
random_filename = str(uuid.uuid4())
165+
cv2.imwrite(f'./saved/{random_filename}.jpg', resized)
166+
167+
vc.release()
168+
cv2.destroyAllWindows()
2.71 KB
2.72 KB
2.73 KB
2.98 KB
2.91 KB
2.86 KB
2.76 KB

0 commit comments

Comments
 (0)