From ff21718d6f63705e563ccff56306d28cd248deaa Mon Sep 17 00:00:00 2001
From: Yathish N V <48596315+Yathish27@users.noreply.github.com>
Date: Thu, 3 Jun 2021 20:00:20 +0530
Subject: [PATCH 1/4] Create Capstone Project
---
Capstone Project | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Capstone Project
diff --git a/Capstone Project b/Capstone Project
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Capstone Project
@@ -0,0 +1 @@
+
From 03080d17ad08cff8c50197ea0978ce56c7aec1fc Mon Sep 17 00:00:00 2001
From: Yathish N V <48596315+Yathish27@users.noreply.github.com>
Date: Thu, 3 Jun 2021 20:05:47 +0530
Subject: [PATCH 2/4] Delete Capstone Project
---
Capstone Project | 1 -
1 file changed, 1 deletion(-)
delete mode 100644 Capstone Project
diff --git a/Capstone Project b/Capstone Project
deleted file mode 100644
index 8b13789..0000000
--- a/Capstone Project
+++ /dev/null
@@ -1 +0,0 @@
-
From 46fd1b1472865f3481d90d6b0489116e9673b463 Mon Sep 17 00:00:00 2001
From: Yathish N V <48596315+Yathish27@users.noreply.github.com>
Date: Thu, 3 Jun 2021 20:06:31 +0530
Subject: [PATCH 3/4] Create Finger Count
---
Capstone Project/Finger Count Project/Finger Count | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Capstone Project/Finger Count Project/Finger Count
diff --git a/Capstone Project/Finger Count Project/Finger Count b/Capstone Project/Finger Count Project/Finger Count
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Capstone Project/Finger Count Project/Finger Count
@@ -0,0 +1 @@
+
From af2ed7a1f27d31ec18603a0bbd52da1028d1a7e0 Mon Sep 17 00:00:00 2001
From: Yathish N V <48596315+Yathish27@users.noreply.github.com>
Date: Thu, 3 Jun 2021 20:07:36 +0530
Subject: [PATCH 4/4] Capstone added
---
.../Finger Count Project/Finger_Count.ipynb | 357 ++++++++++++++++++
.../Finger Count Project/Untitled.ipynb | 213 +++++++++++
.../Finger Count Project/hand_convex.png | Bin 0 -> 4654 bytes
3 files changed, 570 insertions(+)
create mode 100644 Capstone Project/Finger Count Project/Finger_Count.ipynb
create mode 100644 Capstone Project/Finger Count Project/Untitled.ipynb
create mode 100644 Capstone Project/Finger Count Project/hand_convex.png
diff --git a/Capstone Project/Finger Count Project/Finger_Count.ipynb b/Capstone Project/Finger Count Project/Finger_Count.ipynb
new file mode 100644
index 0000000..cbe52e4
--- /dev/null
+++ b/Capstone Project/Finger Count Project/Finger_Count.ipynb
@@ -0,0 +1,357 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Capstone Project\n",
+ "\n",
+ "## Finger Detection and Counting"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cv2\n",
+ "import numpy as np\n",
+ "\n",
+ "# Used for distance calculation later on\n",
+ "from sklearn.metrics import pairwise"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Global Variables\n",
+ "\n",
+ "We will use these as we go along."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# This background will be a global variable that we update through a few functions\n",
+ "background = None\n",
+ "\n",
+ "# Start with a halfway point between 0 and 1 of accumulated weight\n",
+ "accumulated_weight = 0.5\n",
+ "\n",
+ "\n",
+ "# Manually set up our ROI for grabbing the hand.\n",
+ "# Feel free to change these. I just chose the top right corner for filming.\n",
+ "roi_top = 20\n",
+ "roi_bottom = 300\n",
+ "roi_right = 300\n",
+ "roi_left = 600\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Finding Average Background Value\n",
+ "\n",
+ "The function calculates the weighted sum of the input image src and the accumulator dst so that dst becomes a running average of a frame sequence:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def calc_accum_avg(frame, accumulated_weight):\n",
+ " '''\n",
+ " Given a frame and a previous accumulated weight, computed the weighted average of the image passed in.\n",
+ " '''\n",
+ " \n",
+ " # Grab the background\n",
+ " global background\n",
+ " \n",
+ " # For first time, create the background from a copy of the frame.\n",
+ " if background is None:\n",
+ " background = frame.copy().astype(\"float\")\n",
+ " return None\n",
+ "\n",
+ " # compute weighted average, accumulate it and update the background\n",
+ " cv2.accumulateWeighted(frame, background, accumulated_weight)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Segment the Hand Region in Frame"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def segment(frame, threshold=25):\n",
+ " global background\n",
+ " \n",
+ " # Calculates the Absolute Differentce between the backgroud and the passed in frame\n",
+ " diff = cv2.absdiff(background.astype(\"uint8\"), frame)\n",
+ "\n",
+ " # Apply a threshold to the image so we can grab the foreground\n",
+ " # We only need the threshold, so we will throw away the first item in the tuple with an underscore _\n",
+ " _ , thresholded = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY)\n",
+ "\n",
+ " # Grab the external contours form the image\n",
+ " # Again, only grabbing what we need here and throwing away the rest\n",
+ " image, contours, hierarchy = cv2.findContours(thresholded.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
+ "\n",
+ " # If length of contours list is 0, then we didn't grab any contours!\n",
+ " if len(contours) == 0:\n",
+ " return None\n",
+ " else:\n",
+ " # Given the way we are using the program, the largest external contour should be the hand (largest by area)\n",
+ " # This will be our segment\n",
+ " hand_segment = max(contours, key=cv2.contourArea)\n",
+ " \n",
+ " # Return both the hand segment and the thresholded hand image\n",
+ " return (thresholded, hand_segment)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Counting Fingers with a Convex Hull\n",
+ "\n",
+ "We just calculated the external contour of the hand. Now using that segmented hand, let's see how to calculate fingers. Then we can count how many are up!\n",
+ "\n",
+ "Example of ConvexHulls:\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def count_fingers(thresholded, hand_segment):\n",
+ " \n",
+ " \n",
+ " # Calculated the convex hull of the hand segment\n",
+ " conv_hull = cv2.convexHull(hand_segment)\n",
+ " \n",
+ " # Now the convex hull will have at least 4 most outward points, on the top, bottom, left, and right.\n",
+ " # Let's grab those points by using argmin and argmax. Keep in mind, this would require reading the documentation\n",
+ " # And understanding the general array shape returned by the conv hull.\n",
+ "\n",
+ " # Find the top, bottom, left , and right.\n",
+ " # Then make sure they are in tuple format\n",
+ " top = tuple(conv_hull[conv_hull[:, :, 1].argmin()][0])\n",
+ " bottom = tuple(conv_hull[conv_hull[:, :, 1].argmax()][0])\n",
+ " left = tuple(conv_hull[conv_hull[:, :, 0].argmin()][0])\n",
+ " right = tuple(conv_hull[conv_hull[:, :, 0].argmax()][0])\n",
+ "\n",
+ " # In theory, the center of the hand is half way between the top and bottom and halfway between left and right\n",
+ " cX = (left[0] + right[0]) // 2\n",
+ " cY = (top[1] + bottom[1]) // 2\n",
+ "\n",
+ " # find the maximum euclidean distance between the center of the palm\n",
+ " # and the most extreme points of the convex hull\n",
+ " \n",
+ " # Calculate the Euclidean Distance between the center of the hand and the left, right, top, and bottom.\n",
+ " distance = pairwise.euclidean_distances([(cX, cY)], Y=[left, right, top, bottom])[0]\n",
+ " \n",
+ " # Grab the largest distance\n",
+ " max_distance = distance.max()\n",
+ " \n",
+ " # Create a circle with 90% radius of the max euclidean distance\n",
+ " radius = int(0.8 * max_distance)\n",
+ " circumference = (2 * np.pi * radius)\n",
+ "\n",
+ " # Not grab an ROI of only that circle\n",
+ " circular_roi = np.zeros(thresholded.shape[:2], dtype=\"uint8\")\n",
+ " \n",
+ " # draw the circular ROI\n",
+ " cv2.circle(circular_roi, (cX, cY), radius, 255, 10)\n",
+ " \n",
+ " \n",
+ " # Using bit-wise AND with the cirle ROI as a mask.\n",
+ " # This then returns the cut out obtained using the mask on the thresholded hand image.\n",
+ " circular_roi = cv2.bitwise_and(thresholded, thresholded, mask=circular_roi)\n",
+ "\n",
+ " # Grab contours in circle ROI\n",
+ " image, contours, hierarchy = cv2.findContours(circular_roi.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)\n",
+ "\n",
+ " # Finger count starts at 0\n",
+ " count = 0\n",
+ "\n",
+ " # loop through the contours to see if we count any more fingers.\n",
+ " for cnt in contours:\n",
+ " \n",
+ " # Bounding box of countour\n",
+ " (x, y, w, h) = cv2.boundingRect(cnt)\n",
+ "\n",
+ " # Increment count of fingers based on two conditions:\n",
+ " \n",
+ " # 1. Contour region is not the very bottom of hand area (the wrist)\n",
+ " out_of_wrist = ((cY + (cY * 0.25)) > (y + h))\n",
+ " \n",
+ " # 2. Number of points along the contour does not exceed 25% of the circumference of the circular ROI (otherwise we're counting points off the hand)\n",
+ " limit_points = ((circumference * 0.25) > cnt.shape[0])\n",
+ " \n",
+ " \n",
+ " if out_of_wrist and limit_points:\n",
+ " count += 1\n",
+ "\n",
+ " return count"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Run Program"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cam = cv2.VideoCapture(0)\n",
+ "\n",
+ "# Intialize a frame count\n",
+ "num_frames = 0\n",
+ "\n",
+ "# keep looping, until interrupted\n",
+ "while True:\n",
+ " # get the current frame\n",
+ " ret, frame = cam.read()\n",
+ "\n",
+ " # flip the frame so that it is not the mirror view\n",
+ " frame = cv2.flip(frame, 1)\n",
+ "\n",
+ " # clone the frame\n",
+ " frame_copy = frame.copy()\n",
+ "\n",
+ " # Grab the ROI from the frame\n",
+ " roi = frame[roi_top:roi_bottom, roi_right:roi_left]\n",
+ "\n",
+ " # Apply grayscale and blur to ROI\n",
+ " gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)\n",
+ " gray = cv2.GaussianBlur(gray, (7, 7), 0)\n",
+ "\n",
+ " # For the first 30 frames we will calculate the average of the background.\n",
+ " # We will tell the user while this is happening\n",
+ " if num_frames < 60:\n",
+ " calc_accum_avg(gray, accumulated_weight)\n",
+ " if num_frames <= 59:\n",
+ " cv2.putText(frame_copy, \"WAIT! GETTING BACKGROUND AVG.\", (200, 400), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)\n",
+ " cv2.imshow(\"Finger Count\",frame_copy)\n",
+ " \n",
+ " else:\n",
+ " # now that we have the background, we can segment the hand.\n",
+ " \n",
+ " # segment the hand region\n",
+ " hand = segment(gray)\n",
+ "\n",
+ " # First check if we were able to actually detect a hand\n",
+ " if hand is not None:\n",
+ " \n",
+ " # unpack\n",
+ " thresholded, hand_segment = hand\n",
+ "\n",
+ " # Draw contours around hand segment\n",
+ " cv2.drawContours(frame_copy, [hand_segment + (roi_right, roi_top)], -1, (255, 0, 0),1)\n",
+ "\n",
+ " # Count the fingers\n",
+ " fingers = count_fingers(thresholded, hand_segment)\n",
+ "\n",
+ " # Display count\n",
+ " cv2.putText(frame_copy, str(fingers), (70, 45), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)\n",
+ "\n",
+ " # Also display the thresholded image\n",
+ " cv2.imshow(\"Thesholded\", thresholded)\n",
+ "\n",
+ " # Draw ROI Rectangle on frame copy\n",
+ " cv2.rectangle(frame_copy, (roi_left, roi_top), (roi_right, roi_bottom), (0,0,255), 5)\n",
+ "\n",
+ " # increment the number of frames for tracking\n",
+ " num_frames += 1\n",
+ "\n",
+ " # Display the frame with segmented hand\n",
+ " cv2.imshow(\"Finger Count\", frame_copy)\n",
+ "\n",
+ "\n",
+ " # Close windows with Esc\n",
+ " k = cv2.waitKey(1) & 0xFF\n",
+ "\n",
+ " if k == 27:\n",
+ " break\n",
+ "\n",
+ "# Release the camera and destroy all the windows\n",
+ "cam.release()\n",
+ "cv2.destroyAllWindows()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/Capstone Project/Finger Count Project/Untitled.ipynb b/Capstone Project/Finger Count Project/Untitled.ipynb
new file mode 100644
index 0000000..526ddb3
--- /dev/null
+++ b/Capstone Project/Finger Count Project/Untitled.ipynb
@@ -0,0 +1,213 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cv2\n",
+ "import numpy as np\n",
+ "\n",
+ "from sklearn.metrics import pairwise"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "background = None\n",
+ "\n",
+ "accumulated_weight = 0.5\n",
+ "\n",
+ "roi_top = 20\n",
+ "roi_bottom = 300\n",
+ "roi_right = 300\n",
+ "roi_left = 600"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def calc_accum_avg(frame,accumulated_weight):\n",
+ " \n",
+ " global background\n",
+ " \n",
+ " if background is None:\n",
+ " background = frame.copy().astype('float')\n",
+ " return None\n",
+ " \n",
+ " cv2.accumulateWeighted(frame,background,accumulated_weight)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def segment(frame,threshold_min=25):\n",
+ " \n",
+ " diff = cv2.absdiff(background.astype('uint8'),frame)\n",
+ " \n",
+ " ret,thresholded = cv2.threshold(diff,threshold_min,255,cv2.THRESH_BINARY)\n",
+ " \n",
+ " image,contours,hierarchy = cv2.findContours(thresholded.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)\n",
+ " \n",
+ " if len(contours) == 0:\n",
+ " return None\n",
+ " \n",
+ " else:\n",
+ " # ASSUMING THE LARGEST EXTERNAL CONTOUR IN ROI, IS THE HAND\n",
+ " hand_segment = max(contours,key=cv2.contourArea)\n",
+ " \n",
+ " return (thresholded,hand_segment)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def count_fingers(thresholded,hand_segment):\n",
+ " \n",
+ " conv_hull =cv2.convexHull(hand_segment)\n",
+ " \n",
+ " # TOP\n",
+ " top = tuple(conv_hull[conv_hull[:, :, 1].argmin()][0])\n",
+ " bottom = tuple(conv_hull[conv_hull[:, :, 1].argmax()][0])\n",
+ " left = tuple(conv_hull[conv_hull[:, :, 0].argmin()][0])\n",
+ " right = tuple(conv_hull[conv_hull[:, :, 0].argmax()][0])\n",
+ " \n",
+ " cX = (left[0] + right[0]) // 2\n",
+ " cY = (top[1] + bottom[1]) // 2\n",
+ " \n",
+ " distance = pairwise.euclidean_distances([cX,cY],Y=[left,right,top,bottom])[0]\n",
+ " \n",
+ " max_distance = distance.max()\n",
+ " \n",
+ " radius = int(0.9*max_distance)\n",
+ " circumfrence = (2*np.pi*radius)\n",
+ "\n",
+ " \n",
+ " circular_roi = np.zeros(thresholded[:2],dtype='uint8')\n",
+ " \n",
+ " cv2.circle(circular_roi,(cX,cY),radius,255,10)\n",
+ " \n",
+ " circular_roi = cv2.bitwise_and(thresholded,thresholded,mask=circular_roi)\n",
+ " \n",
+ " image,contours,hierarchy = cv2.findContours(circular_roi.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)\n",
+ " \n",
+ " count = 0\n",
+ " \n",
+ " for cnt in contours:\n",
+ " \n",
+ " (x,y,w,h) = cv2.boundingRect(cnt)\n",
+ " \n",
+ " out_of_wrist = (cY + (cY*0.25)) > (y+h)\n",
+ " \n",
+ " limit_points = ((circumfrence*0.25) > cnt.shape[0])\n",
+ " \n",
+ " if out_of_wrist and limit_points:\n",
+ " count += 1\n",
+ " \n",
+ " return count"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cam = cv2.VideoCapture(0)\n",
+ "\n",
+ "num_frames = 0\n",
+ "\n",
+ "while True:\n",
+ " \n",
+ " ret, frame = cam.read()\n",
+ " \n",
+ " frame_copy = frame.copy()\n",
+ " \n",
+ " roi = frame[roi_top:roi_bottom,roi_right:roi_left]\n",
+ " \n",
+ " gray = cv2.cvtColor(roi,cv2.COLOR_BGR2GRAY)\n",
+ " \n",
+ " gray = cv2.GaussianBlur(gray,(7,7),0)\n",
+ " \n",
+ " if num_frames < 60:\n",
+ " calc_accum_avg(gray,accumulated_weight)\n",
+ " \n",
+ " if num_frames <= 59:\n",
+ " cv2.putText(frame_copy,'WAIT. GETTING BACKGROUND',(200,300),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)\n",
+ " cv2.imshow('Finger Count',frame_copy)\n",
+ " else:\n",
+ " \n",
+ " hand = segment(gray)\n",
+ " \n",
+ " if hand is not None:\n",
+ " \n",
+ " thresholded , hand_segment = hand\n",
+ " \n",
+ " # DRAWS CONTOURS AROUND REAL HAND IN LIVE STREAM\n",
+ " cv2.drawContours(frame_copy,[hand_segment+(roi_right,roi_top)],-1,(255,0,0),5)\n",
+ " \n",
+ " fingers = count_fingers(thresholded,hand_segment)\n",
+ " \n",
+ " cv2.putText(frame_copy,str(fingers),(70,50),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)\n",
+ " \n",
+ " cv2.imshow('Thresholded',thresholded)\n",
+ " \n",
+ " cv2.rectangle(frame_copy,(roi_left,roi_top),(roi_right,roi_bottom),(0,0,255),5)\n",
+ " \n",
+ " num_frames += 1\n",
+ " \n",
+ " cv2.imshow('Finger Count',frame_copy)\n",
+ " \n",
+ " k = cv2.waitKey(1) & 0xFF\n",
+ " \n",
+ " if k == 27:\n",
+ " break\n",
+ " \n",
+ "cam.release()\n",
+ "cv2.destroyAllWindows()\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/Capstone Project/Finger Count Project/hand_convex.png b/Capstone Project/Finger Count Project/hand_convex.png
new file mode 100644
index 0000000000000000000000000000000000000000..8f67ca1dd68fc294fc4996dea0cbed713c4f0e76
GIT binary patch
literal 4654
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
literal 0
HcmV?d00001