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