Skip to content

Commit 9c9eab2

Browse files
Merge pull request #1591 from sohil1234/fingers
Fingers Count using OpenCV
2 parents 83c48fb + f1a2d60 commit 9c9eab2

File tree

3 files changed

+285
-0
lines changed

3 files changed

+285
-0
lines changed

Finger_count_openCV/README.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Number of fingers using OpenCV
2+
3+
Counts the number of fingers shown by user using OpenCV library in Python
4+
5+
## Setup instructions
6+
7+
8+
9+
Navigate to the folder and run the following command to install the required python libraries
10+
11+
pip install -r requirements.txt
12+
13+
Then run the command
14+
15+
python main.py
16+
17+
18+
## Working behind Hand gesture recogonition:
19+
20+
### grey filter
21+
22+
grey = cv2.cvtColor(crop_image,cv2.COLOR_BGR2GRAY)
23+
24+
It is a very important step that needs to be performed in order to get the desired result
25+
26+
27+
### Gaussian Blur
28+
29+
blur = cv2.GaussianBlur(grey,(35,35),0)
30+
31+
In Gaussian Blur operation, the image is convolved with a Gaussian filter instead of the box filter. The Gaussian filter is a low-pass filter that removes the high-frequency components are reduced.
32+
33+
You can perform this operation on an image using the Gaussianblur() method of the imgproc class. Following is the syntax of this method −
34+
35+
GaussianBlur(src, dst, ksize, sigmaX)
36+
37+
- src − A Mat object representing the source (input image) for this operation.
38+
39+
- dst − A Mat object representing the destination (output image) for this operation.
40+
41+
- ksize − A Size object representing the size of the kernel.
42+
43+
- sigmaX − A variable of the type double representing the Gaussian kernel standard deviation in X direction.
44+
45+
46+
### Thresholding the Image
47+
48+
Here, the matter is straight-forward. For every pixel, the same threshold value is applied. If the pixel value is smaller than the threshold, it is set to 0, otherwise it is set to a maximum value. The function cv.threshold is used to apply the thresholding. The first argument is the source image, which should be a grayscale image. The second argument is the threshold value which is used to classify the pixel values. The third argument is the maximum value which is assigned to pixel values exceeding the threshold. OpenCV provides different types of thresholding which is given by the fourth parameter of the function. Basic thresholding as described above is done by using the type cv.THRESH_BINARY. All simple thresholding types are:
49+
50+
- cv2.THRESH_BINARY
51+
- cv2.THRESH_BINARY_INV
52+
- cv2.THRESH_TRUNC
53+
- cv2.THRESH_TOZERO
54+
- cv2.THRESH_TOZERO_INV
55+
56+
In this project cv2.THRESH_BINARY_INV is used
57+
58+
ret,thresh= cv2.threshold(blur,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
59+
60+
### Binary threshold inverted
61+
62+
![binary](https://user-images.githubusercontent.com/93030904/208385197-96bb4e66-e4eb-439f-bf25-5008b22704d0.jpg)
63+
64+
65+
66+
### Otsu's Binarization
67+
68+
In global thresholding, we used an arbitrary chosen value as a threshold. In contrast, Otsu's method avoids having to choose a value and determines it automatically.
69+
70+
Consider an image with only two distinct image values (bimodal image), where the histogram would only consist of two peaks. A good threshold would be in the middle of those two values. Similarly, Otsu's method determines an optimal global threshold value from the image histogram.
71+
72+
In order to do so, the cv.threshold() function is used, where cv.THRESH_OTSU is passed as an extra flag. The threshold value can be chosen arbitrary. The algorithm then finds the optimal threshold value which is returned as the first output.
73+
74+
Check out the example below. The input image is a noisy image. In the first case, global thresholding with a value of 127 is applied. In the second case, Otsu's thresholding is applied directly. In the third case, the image is first filtered with a 5x5 gaussian kernel to remove the noise, then Otsu thresholding is applied. See how noise filtering improves the result.
75+
76+
![otsu](https://user-images.githubusercontent.com/93030904/168461709-9bdf4f63-0e6f-4c8a-892b-0e47fbbffd0e.jpeg)
77+
78+
### Contours
79+
80+
Contours can be explained simply as a curve joining all the continuous points (along the boundary), having same color or intensity. The contours are a useful tool for shape analysis and object detection and recognition.
81+
82+
For better accuracy, use binary images. So before finding contours, apply threshold or canny edge detection.
83+
Since OpenCV 3.2, findContours() no longer modifies the source image but returns a modified image as the first of three return parameters.
84+
In OpenCV, finding contours is like finding white object from black background. So remember, object to be found should be white and background should be black.
85+
86+
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
87+
88+
# Finding the contour with maximum area
89+
contour = max(contours, key=lambda x: cv2.contourArea(x))
90+
91+
# Drawing the contour on the image
92+
cv2.drawContours(drawing, [contour], -1, (0, 255, 0), 0)
93+
94+
### contour approximation method
95+
96+
This is the third argument in cv2.findContours function. What does it denote actually?
97+
98+
Above, we told that contours are the boundaries of a shape with same intensity. It stores the (x,y) coordinates of the boundary of a shape. But does it store all the coordinates ? That is specified by this contour approximation method.
99+
100+
If you pass cv.CHAIN_APPROX_NONE, all the boundary points are stored. But actually do we need all the points? For eg, you found the contour of a straight line. Do you need all the points on the line to represent that line? No, we need just two end points of that line. This is what cv.CHAIN_APPROX_SIMPLE does. It removes all redundant points and compresses the contour, thereby saving memory.
101+
102+
Below image of a rectangle demonstrate this technique. Just draw a circle on all the coordinates in the contour array (drawn in blue color). First image shows points I got with cv2.CHAIN_APPROX_NONE (734 points) and second image shows the one with cv2.CHAIN_APPROX_SIMPLE (only 4 points). See, how much memory it saves!!!
103+
104+
105+
![Chain Approximattion](./approx.jpg "Approximation simple")
106+
107+
108+
### Convex Hull
109+
110+
hull = cv2.convexHull(contour, returnPoints=False)
111+
112+
Finds the convex hull of a point set.
113+
114+
The function cv2.convexHull finds the convex hull of a 2D point set using the Sklansky's algorithm [194] that has O(N logN) complexity in the current implementation.
115+
116+
Parameters
117+
- points Input 2D point set, stored in std::vector or Mat.
118+
- hull Output convex hull. It is either an integer vector of indices or vector of points. In the first case, the hull elements are 0-based indices of the convex hull points in the original array (since the set of convex hull points is a subset of the original point set). In the second case, hull elements are the convex hull points themselves.
119+
- clockwise Orientation flag. If it is true, the output convex hull is oriented clockwise. Otherwise, it is oriented counter-clockwise. The assumed coordinate system has its X axis pointing to the right, and its Y axis pointing upwards.
120+
- returnPoints Operation flag. In case of a matrix, when the flag is true, the function returns convex hull points. Otherwise, it returns indices of the convex hull points.
121+
122+
### Convexity Defects
123+
124+
defects = cv2.convexityDefects(contour, hull)
125+
126+
127+
![Convexity Defects](https://user-images.githubusercontent.com/93030904/168461387-94120ae1-553a-4516-9913-e956b959e9fd.png "Convexity defect")
128+
129+
Parameters
130+
- contour: Input contour.
131+
- convexhull: Convex hull obtained using convexHull that should contain indices of the contour points that make the hull.
132+
- convexityDefects: The output vector of convexity defects. In C++ and the new Python/Java interface each convexity defect is represented as 4-element integer vector (a.k.a. Vec4i): (start_index, end_index, farthest_pt_index, fixpt_depth), where indices are 0-based indices in the original contour of the convexity defect beginning, end and the farthest point, and fixpt_depth is fixed-point approximation (with 8 fractional bits) of the distance between the farthest contour point and the hull. That is, to get the floating-point value of the depth will be fixpt_depth/256.0.
133+
134+
135+
### Math for calculating the angles between fingers
136+
137+
for i in range(defects.shape[0]):
138+
s, e, f, d = defects[i, 0]
139+
start = tuple(contour[s][0])
140+
end = tuple(contour[e][0])
141+
far = tuple(contour[f][0])
142+
143+
# calculating the angle using cosine formula
144+
145+
a = math.sqrt((start[0] - end[0]) ** 2 + (start[1] - end[1]) ** 2)
146+
b = math.sqrt((-start[0] + far[0]) ** 2 + (-start[1] + far[1]) ** 2)
147+
c = math.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)
148+
angle = (math.acos((b**2 + c**2 - a**2) / (2*b*c)))*57
149+
150+
if angle <= 90:
151+
count_defects += 1
152+
cv2.circle(crop_image, far, 1, [0, 0, 255], -1)
153+
cv2.line(crop_image, start, end, [0, 255, 0], 2)
154+
155+
Two fingers will create a angle less than 90 deg between them, Thus we count the number of angles less than 90 in the contours and add one to get the number of fingers.
156+
157+
## Author(s)
158+
159+
Sohil Khan
160+
<br> Github Username: sohil1234
161+

Finger_count_openCV/main.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import numpy as np
2+
import cv2
3+
import math
4+
5+
# video capture
6+
capture = cv2.VideoCapture(0)
7+
8+
while True:
9+
success,img = capture.read()
10+
11+
12+
# Frame Cropping
13+
cv2.rectangle(img,(20,20),(400,300),(0,255,0),0)
14+
crop_image= img[20:300,20:400]
15+
16+
# TODO: Grey Filter (pass crop_image)
17+
# grey = your code
18+
grey = cv2.cvtColor(crop_image,cv2.COLOR_BGR2GRAY)
19+
20+
# TODO: Gaussian Blur to smoothen the image (pass grey)
21+
# blur = your code
22+
blur = cv2.GaussianBlur(grey,(35,35),0)
23+
24+
25+
# TODO: thresholding the image using Binary inversion + OTSU
26+
# ret,thresh= your code
27+
ret,thresh= cv2.threshold(blur,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
28+
29+
30+
# show threshold
31+
cv2.imshow("Threshold",thresh)
32+
33+
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
34+
try:
35+
# TODO: contour with maximum area
36+
# contour = your code
37+
contour = max(contours, key=lambda x: cv2.contourArea(x))
38+
39+
# bounding rectangle for the contour
40+
x, y, w, h = cv2.boundingRect(contour)
41+
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 0)
42+
43+
# TODO: create hull
44+
#hull = your code (important: do not use returnPoints argument)
45+
hull = cv2.convexHull(contour)
46+
47+
drawing = np.zeros(img.shape, np.uint8)
48+
cv2.drawContours(drawing, [contour], -1, (0, 255, 0), 0)
49+
cv2.drawContours(drawing,[hull],-1,(0,0,255),0)
50+
51+
#TODO: finding convex hull
52+
#hull = your code (important: use returnPoints argument as False)
53+
hull = cv2.convexHull(contour, returnPoints=False)
54+
55+
# TODO: finding convexity defects
56+
# defects = your code
57+
defects = cv2.convexityDefects(contour, hull)
58+
59+
60+
61+
count_defects = 0
62+
#TODO: refer to the math for calculating the angle part of README
63+
for i in range(defects.shape[0]):
64+
s,e,f,d = defects[i,0]
65+
start = tuple(contour[s][0])
66+
end = tuple(contour[e][0])
67+
far = tuple(contour[f][0])
68+
69+
a = math.sqrt((start[0] - end[0]) ** 2 + (start[1] - end[1]) ** 2)
70+
b = math.sqrt((-start[0] + far[0]) ** 2 + (-start[1] + far[1]) ** 2)
71+
c = math.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)
72+
angle = (math.acos((b**2 + c**2 - a**2) / (2*b*c)))*57
73+
74+
if angle <= 90:
75+
count_defects += 1
76+
cv2.circle(crop_image, far, 1, [0, 0, 255], -1)
77+
cv2.line(crop_image, start, end, [0, 255, 0], 2)
78+
79+
# output
80+
# TODO: Complete the logic
81+
if count_defects == 0:
82+
cv2.putText(img, "ONE",(50,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 2)
83+
# elif count_defects == 1:
84+
elif count_defects == 1:
85+
cv2.putText(img, "TWO",(50,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 2)
86+
elif count_defects == 2:
87+
cv2.putText(img, "THREE",(50,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 2)
88+
elif count_defects == 3:
89+
cv2.putText(img, "FOUR",(50,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 2)
90+
elif count_defects == 4:
91+
cv2.putText(img, "FIVE",(50,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 2)
92+
93+
94+
95+
else:
96+
97+
pass
98+
99+
100+
except:
101+
pass
102+
103+
cv2.imshow("Gesture",img)
104+
all_image = np.hstack((drawing,img))
105+
106+
cv2.imshow('contours',all_image)
107+
108+
if cv2.waitKey(1)==ord('q'):
109+
break
110+
111+
112+
capture.release()
113+
cv2.destroyAllWindows()
114+
115+
116+
117+
118+
119+
120+
121+
122+

Finger_count_openCV/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
numpy==1.22.3
2+
opencv-python==4.5.5.64

0 commit comments

Comments
 (0)