From 408411b2b759e6ab3651ff596b4b320fecc4b196 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Sat, 3 Mar 2018 15:18:46 -0500 Subject: [PATCH 01/39] Create Project Proposal.md --- Project Proposal.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Project Proposal.md diff --git a/Project Proposal.md b/Project Proposal.md new file mode 100644 index 00000000..2d0f712c --- /dev/null +++ b/Project Proposal.md @@ -0,0 +1,21 @@ +# Drawing with Fingers +The main idea of our project is to have an interface where someone can use a webcam to trace their finger and use their finger to paint on the screen. We will do a lot of image processing and finger tracking work with OpenCV. + +## _Minimal Viable Product_: +* A display that can trace finger movement in black on a white drawing canvas +## _Stretch Goal_: +* Having a way to turn off the paint so it’s not continuous +* Having two paint brushes/brushes whose hue can be tuned in real time + +## Learning Goals for each person: +Tommy: I want to get better at programming with another person, and also with making sure that I use Git properly. +Josh: My goal is to get to use OpenCV more which I have evaded ever since the beginning of last semester. + +## Possible Libraries to Use: +The libraries that we will probably be using are OpenCV and Pygame. OpenCV will be how we can utilize the webcams on laptops to keep track of finger movements in the video frame. Pygame will be used to creative an interactive drawing canvas that will put down colors based on the finger movements. The saturation of the colors will stay constant. Different finger speed will then result in different values. We are hoping to incorporate the other hand/fingers in an interesting way so that we can adjust the hue of the brushes. + +## Goal before the mid-project check-in +By mid-project check in, we plan on having a skeleton of our code decided, and have a plan for how to implement tracking the finger. We also plan on knowing if pygame and OpenCV have all the resources we need for this project. + +## Risks??? +The biggest risks might include if we don’t plan out our implementation well and if we have a lot of difficulty with classes. From 30cc2f233c1df6f76fa889e4016a84d8120fd499 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Sat, 3 Mar 2018 15:20:35 -0500 Subject: [PATCH 02/39] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5f822327..7322eb77 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # InteractiveProgramming This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. + +### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From 60b627f85ec6e18782621760a96d56f412649bed Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Tue, 6 Mar 2018 15:16:45 -0500 Subject: [PATCH 03/39] Committing first test of color tracking code March 6 --- paint.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 paint.py diff --git a/paint.py b/paint.py new file mode 100644 index 00000000..2df5b9ab --- /dev/null +++ b/paint.py @@ -0,0 +1,45 @@ + +import cv2 +import numpy as np + +"""capturing video from camera""" +cap = cv2.VideoCapture(0) + +while(True): + #capture frame by frame + ret, frame = cap.read() + frame = cv2.flip(frame,1) + hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + #find specific color + lower_white = np.array([[0, 0, 230]]) + upper_white = np.array([180, 25, 255]) + lower_color = np.array([0,80,50]) + upper_color = np.array([20,100,100]) + lower_red = np.array([150,150,50]) + upper_red = np.array([180,255,150]) + + mask = cv2.inRange(hsv,lower_red, upper_red) + + res = cv2.bitwise_and(frame,frame,mask=mask) + + #display the resulting frame + cv2.imshow('frame',frame) + cv2.imshow('mask', mask) + cv2.imshow('res',res) + if cv2.waitKey(1) & 0xFF == ord('q'): + break +#when finshed, release the capture +cap.release() +cv2.destroyAllWindows() +"""playing video from file +cap = cv2.VideoCapture('test.avi') + +while(cap.isOpened()): + ret, frame = cap.read() + cv2.waitKey(25) + cv2.imshow('frame',frame) + if cv2.waitKey(1) & 0xFF == ord('q'): + break +cap.release() +cv2.destroyAllWindows() +""" From 7f06934c4cf4cc9bfe491ca8140c9150f8c6fb00 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Wed, 7 Mar 2018 17:36:37 -0500 Subject: [PATCH 04/39] Able to track a red object with its center pinpointed --- paint.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/paint.py b/paint.py index 2df5b9ab..7cd83a57 100644 --- a/paint.py +++ b/paint.py @@ -4,6 +4,10 @@ """capturing video from camera""" cap = cv2.VideoCapture(0) +frame_num = 0 +cx = 0 +cy = 0 +path = [] while(True): #capture frame by frame @@ -18,14 +22,38 @@ lower_red = np.array([150,150,50]) upper_red = np.array([180,255,150]) - mask = cv2.inRange(hsv,lower_red, upper_red) + + mask1 = cv2.inRange(hsv, np.array([0, 150, 100]), np.array([5, 255, 255])); + mask2 = cv2.inRange(hsv, np.array([175, 150, 100]), np.array([180, 255, 255])); + mask = mask1 | mask2 + mask = cv2.bilateralFilter(mask, 10, 40, 40) + mask = cv2.blur(mask,(5,5)) res = cv2.bitwise_and(frame,frame,mask=mask) + mask = cv2.blur(mask,(20,20)) + # Getting a contour and the center of the contour + im2,contours,hierarchy = cv2.findContours(mask, 1, 2) + try: + if frame_num > 3 or frame_num == 0: + cnt = contours[0] + M = cv2.moments(cnt) + print(M['m10']/M['m00']) + cx = int(M['m10']/M['m00']) + cy = int(M['m01']/M['m00']) + frame_num = 0 + path.append((cx, cy)) + cv2.circle(res, (cx, cy), 2, (0, 255, 0), -1) + except IndexError: + """""" + for i in range(len()) + frame_num += 1 + # cnts = cv2.findContours(res, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #display the resulting frame cv2.imshow('frame',frame) - cv2.imshow('mask', mask) + #cv2.imshow('mask', mask) cv2.imshow('res',res) + # cv2.imshow('counto', ) if cv2.waitKey(1) & 0xFF == ord('q'): break #when finshed, release the capture From 949a57d238d964bacf957c186536179684b12904 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Wed, 7 Mar 2018 20:39:11 -0500 Subject: [PATCH 05/39] basic drawing ability; need to exclude the outliers --- paint.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/paint.py b/paint.py index 7cd83a57..30ddcf53 100644 --- a/paint.py +++ b/paint.py @@ -1,6 +1,7 @@ import cv2 import numpy as np +import math """capturing video from camera""" cap = cv2.VideoCapture(0) @@ -23,9 +24,13 @@ upper_red = np.array([180,255,150]) - mask1 = cv2.inRange(hsv, np.array([0, 150, 100]), np.array([5, 255, 255])); - mask2 = cv2.inRange(hsv, np.array([175, 150, 100]), np.array([180, 255, 255])); + mask1 = cv2.inRange(hsv, np.array([0, 150, 100]), np.array([2, 255, 255])); + mask2 = cv2.inRange(hsv, np.array([178, 150, 100]), np.array([180, 255, 255])); mask = mask1 | mask2 + + # Attempting Green + # mask = cv2.inRange(hsv, np.array([50,100,100]), np.array([65,255,255])) + mask = cv2.bilateralFilter(mask, 10, 40, 40) mask = cv2.blur(mask,(5,5)) @@ -42,11 +47,30 @@ cy = int(M['m01']/M['m00']) frame_num = 0 path.append((cx, cy)) + # if diffx > 100 or diffy > 100: + # print('too far') + # else: cv2.circle(res, (cx, cy), 2, (0, 255, 0), -1) except IndexError: """""" - for i in range(len()) + for i in range(len(path)): + # TODO: Add if statements to make sure that any outliers + # would be removed from the list or ignored when drawing + # the linewidth + + diffx = math.fabs(cx-path[-1][0]) + print(diffx) + diffy = math.fabs(cy-path[-1][1]) + print(diffy) + if len(path) == 1: + break + # elif math.sqrt(diffx**2+diffy**2) > 30: + break + elif i < (len(path)-1): + cv2.line(res, path[i], path[i+1], (255,0,0), 3) + + frame_num += 1 # cnts = cv2.findContours(res, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #display the resulting frame From eb5357423462ccc94997d907cfdfac62ca22d191 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Fri, 9 Mar 2018 13:23:33 -0500 Subject: [PATCH 06/39] Classified the programs --- canvas.py | 19 +++++++++++++++ fingerTrack.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 9 +++++++ 3 files changed, 92 insertions(+) create mode 100644 canvas.py create mode 100644 fingerTrack.py create mode 100644 main.py diff --git a/canvas.py b/canvas.py new file mode 100644 index 00000000..c89c3ca9 --- /dev/null +++ b/canvas.py @@ -0,0 +1,19 @@ +import numpy as np +import cv2 + + +class canvas(): + + def __init__(self, width, height): + self.width = width + self.height = height + self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) + + def set_color(self, B, G, R): + self.color = (B, G, R) + + def set_bgColor(self, B, G, R): + self.new_canvas[:, :] = self.color + + def show_canvas(self): + cv2.imshow(self.new_canvas) diff --git a/fingerTrack.py b/fingerTrack.py new file mode 100644 index 00000000..be3cceb0 --- /dev/null +++ b/fingerTrack.py @@ -0,0 +1,64 @@ +import numpy as np +import cv2 +import math + + +class finger_track(): + + def __init__(self): + """ + """ + self.frame_num = 0 + self.cx = 0 + self.cy = 0 + self.path = [] + self.red_maskL = [np.array([0, 150, 100]), np.array([178, 150, 100])] + self.red_maskH = [np.array([2, 255, 255]), np.array([180, 255, 255])] + self.refreshDelay = 3 + + def BGR2HSV(self, frame): + """This functions takes in a frame and converts it from BGR to BGR2HSV + """ + return cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + + def red_mask(self, frame): + """This function generates a red mask based on the frame being passed in + """ + mask1 = cv2.inRange(frame, self.red_maskL[0], self.red_maskH[0]) + mask2 = cv2.inRange(frame, self.red_maskL[1], self.red_maskH[1]) + return (mask1 | mask2) + + def find_center(self, mask, target): + im2, contours, hierarchy = cv2.findContours(mask, 1, 2) + try: + if self.frame_num > self.refreshDelay or self.frame_num == 0: + cnt = contours[0] + M = cv2.moments(cnt) + print(M['m10'] / M['m00']) + self.cx = int(M['m10'] / M['m00']) + self.cy = int(M['m01'] / M['m00']) + self.frame_num = 0 + self.path.append((self.cx, self.cy)) + # if diffx > 100 or diffy > 100: + # print('too far') + # else: + cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) + except IndexError: + """""" + + def draw(self, target): + for i in range(len(self.path)): + # TODO: Add if statements to make sure that any outliers + # would be removed from the list or ignored when drawing + # the linewidth + + # diffx = math.fabs(self.cx-self.path[-1][0]) + # print(diffx) + # diffy = math.fabs(cy-path[-1][1]) + # print(diffy) + if len(self.path) == 1: + break + # elif math.sqrt(diffx**2+diffy**2) > 30: + # break + elif i < (len(self.path)-1): + cv2.line(target, self.path[i], self.path[i+1], (255, 0, 0), 3) diff --git a/main.py b/main.py new file mode 100644 index 00000000..c7f9e08e --- /dev/null +++ b/main.py @@ -0,0 +1,9 @@ +from fingerTrack import * +from canvas import * + +def main(): + """ + """ + +if __name__ == "__main__": + main() From 7d3dbb7c596182d08a1bd8d9be7746af7d3ec080 Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Fri, 9 Mar 2018 14:37:04 -0500 Subject: [PATCH 07/39] Completed a mostly working filtering system --- paint.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/paint.py b/paint.py index 30ddcf53..03296b96 100644 --- a/paint.py +++ b/paint.py @@ -9,6 +9,7 @@ cx = 0 cy = 0 path = [] +clearpath = [] while(True): #capture frame by frame @@ -24,7 +25,7 @@ upper_red = np.array([180,255,150]) - mask1 = cv2.inRange(hsv, np.array([0, 150, 100]), np.array([2, 255, 255])); + mask1 = cv2.inRange(hsv, np.array([0, 150, 100]), np.array([1, 255, 255])); mask2 = cv2.inRange(hsv, np.array([178, 150, 100]), np.array([180, 255, 255])); mask = mask1 | mask2 @@ -39,12 +40,14 @@ # Getting a contour and the center of the contour im2,contours,hierarchy = cv2.findContours(mask, 1, 2) try: - if frame_num > 3 or frame_num == 0: + if frame_num > 0 or frame_num == 0: cnt = contours[0] M = cv2.moments(cnt) - print(M['m10']/M['m00']) + # print(M['m10']/M['m00']) cx = int(M['m10']/M['m00']) + # print(cx) cy = int(M['m01']/M['m00']) + # print(cy) frame_num = 0 path.append((cx, cy)) # if diffx > 100 or diffy > 100: @@ -54,23 +57,30 @@ except IndexError: """""" - for i in range(len(path)): - # TODO: Add if statements to make sure that any outliers - # would be removed from the list or ignored when drawing - # the linewidth + # TODO: Add if statements to make sure that any outliers + # would be removed from the list or ignored when drawing + # the linewidth + if len(path) == 1: + clearpath.append(path[0]) + elif len(path) > 2: + pair = path[-2] + # print(pair, pair[0], cx, pair[1], cy) + diffx = abs(cx-pair[0]) + diffy = abs(cy-pair[1]) + distance = math.sqrt(diffx**2+diffy**2) + if distance<10: + clearpath.append(pair) - diffx = math.fabs(cx-path[-1][0]) - print(diffx) - diffy = math.fabs(cy-path[-1][1]) - print(diffy) - if len(path) == 1: + for i in range(len(clearpath)): + if len(clearpath) < 1: break # elif math.sqrt(diffx**2+diffy**2) > 30: - break - elif i < (len(path)-1): - cv2.line(res, path[i], path[i+1], (255,0,0), 3) - + # break + elif i < (len(clearpath)-1): + cv2.line(res, clearpath[i], clearpath[i+1], (255,0,0), 3) + print(path) + print(clearpath) frame_num += 1 # cnts = cv2.findContours(res, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #display the resulting frame From 195081c72e27719497ed0a4823856a124f7f1687 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Fri, 9 Mar 2018 14:44:18 -0500 Subject: [PATCH 08/39] a functional basic canvas --- canvas.py | 24 ++++++++++++++++++++++-- fingerTrack.py | 10 +++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/canvas.py b/canvas.py index c89c3ca9..e79c9fbd 100644 --- a/canvas.py +++ b/canvas.py @@ -12,8 +12,28 @@ def __init__(self, width, height): def set_color(self, B, G, R): self.color = (B, G, R) - def set_bgColor(self, B, G, R): + def set_bgColor(self): self.new_canvas[:, :] = self.color def show_canvas(self): - cv2.imshow(self.new_canvas) + cv2.imshow('newCanvas', self.new_canvas) + + def save_drawing(self): + """""" + file_name = input('Please name your drawing: ') + cv2.imwrite(file_name+'.jpg', self.new_canvas) + +if __name__ == "__main__": + canvas1 = canvas(1280, 960) + canvas1.set_color(0, 0, 0) + canvas1.set_bgColor() + # cam = cv2.VideoCapture(0) + # print(cam.get(3), cam.get(4)) + while True: + canvas1.show_canvas() + if cv2.waitKey(1) & 0xFF == ord('s'): + canvas1.save_drawing() + break + elif cv2.waitKey(1) & 0xFF == ord('q'): + break + cv2.destroyAllWindows() diff --git a/fingerTrack.py b/fingerTrack.py index be3cceb0..dfd87b3f 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -47,15 +47,15 @@ def find_center(self, mask, target): """""" def draw(self, target): + clearpath = [] for i in range(len(self.path)): # TODO: Add if statements to make sure that any outliers # would be removed from the list or ignored when drawing # the linewidth - - # diffx = math.fabs(self.cx-self.path[-1][0]) - # print(diffx) - # diffy = math.fabs(cy-path[-1][1]) - # print(diffy) + diffx = math.abs(self.cx-self.path[-1][0]) + print(diffx) + diffy = math.abs(cy-path[-1][1]) + print(diffy) if len(self.path) == 1: break # elif math.sqrt(diffx**2+diffy**2) > 30: From f4cc57e9424a6c3d566dabb906c4036c681e504c Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Fri, 9 Mar 2018 15:06:02 -0500 Subject: [PATCH 09/39] Updated fingerTrack to be in line with paint.py --- fingerTrack.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/fingerTrack.py b/fingerTrack.py index be3cceb0..644365fc 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -12,9 +12,10 @@ def __init__(self): self.cx = 0 self.cy = 0 self.path = [] + self.clearpath = [] self.red_maskL = [np.array([0, 150, 100]), np.array([178, 150, 100])] - self.red_maskH = [np.array([2, 255, 255]), np.array([180, 255, 255])] - self.refreshDelay = 3 + self.red_maskH = [np.array([1, 255, 255]), np.array([180, 255, 255])] + self.refreshDelay = 0 def BGR2HSV(self, frame): """This functions takes in a frame and converts it from BGR to BGR2HSV @@ -39,26 +40,28 @@ def find_center(self, mask, target): self.cy = int(M['m01'] / M['m00']) self.frame_num = 0 self.path.append((self.cx, self.cy)) - # if diffx > 100 or diffy > 100: - # print('too far') - # else: cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) except IndexError: """""" def draw(self, target): + if len(self.path) == 1: + clearpath.append(path[0]) + elif len(self.path) > 2: + pair = self.path[-2] + # print(pair, pair[0], cx, pair[1], cy) + diffx = abs(self.cx-pair[0]) + diffy = abs(self.cy-pair[1]) + distance = math.sqrt(diffx**2+diffy**2) + if distance<10: + self.clearpath.append(pair) for i in range(len(self.path)): # TODO: Add if statements to make sure that any outliers # would be removed from the list or ignored when drawing # the linewidth - - # diffx = math.fabs(self.cx-self.path[-1][0]) - # print(diffx) - # diffy = math.fabs(cy-path[-1][1]) - # print(diffy) - if len(self.path) == 1: - break + # if len(self.clearpath) < 1: + # break # elif math.sqrt(diffx**2+diffy**2) > 30: # break - elif i < (len(self.path)-1): - cv2.line(target, self.path[i], self.path[i+1], (255, 0, 0), 3) + if i < (len(self.clearpath)-1): + cv2.line(target, self.clearpath[i], self.path[i+1], (255, 0, 0), 3) From 8fcd335ff042fb0887ea92e0a687f424fec0fc52 Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Fri, 9 Mar 2018 18:33:05 -0500 Subject: [PATCH 10/39] Added the disappearing tail of paint --- fingerTrack.py | 35 ++++++++++++++++++++++------------- paint.py | 10 +++++----- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/fingerTrack.py b/fingerTrack.py index 6ce30cae..d93bca7b 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -55,17 +55,26 @@ def draw(self, target): distance = math.sqrt(diffx**2+diffy**2) if distance<10: self.clearpath.append(pair) - for i in range(len(self.path)): - # TODO: Add if statements to make sure that any outliers - # would be removed from the list or ignored when drawing - # the linewidth - diffx = math.abs(self.cx-self.path[-1][0]) - print(diffx) - diffy = math.abs(cy-path[-1][1]) - print(diffy) - if len(self.path) == 1: + for i in range(len(self.clearpath)): + if len(self.clearpath) < 1: break - # elif math.sqrt(diffx**2+diffy**2) > 30: - # break - if i < (len(self.clearpath)-1): - cv2.line(target, self.clearpath[i], self.path[i+1], (255, 0, 0), 3) + elif i<(len(self.clearpath)-1)<21: + cv2.line(res, self.clearpath[i], self.clearpath[i+1], (255,0,0), 3) + elif 20 < i < (len(self.clearpath)-1): + cv2.rectangle(res, (0,0), (600, 400), (0,0,0)) + for j in range(20): + cv2.line(res, self.clearpath[-(j+1)], self.clearpath[-(j+2)], (255,0,0), 3) + # for i in range(len(self.path)): + # # TODO: Add if statements to make sure that any outliers + # # would be removed from the list or ignored when drawing + # # the linewidth + # diffx = math.abs(self.cx-self.path[-1][0]) + # print(diffx) + # diffy = math.abs(cy-path[-1][1]) + # print(diffy) + # if len(self.path) == 1: + # break + # # elif math.sqrt(diffx**2+diffy**2) > 30: + # # break + # if i < (len(self.clearpath)-1): + # cv2.line(target, self.clearpath[i], self.path[i+1], (255, 0, 0), 3) diff --git a/paint.py b/paint.py index 03296b96..60936467 100644 --- a/paint.py +++ b/paint.py @@ -74,13 +74,13 @@ for i in range(len(clearpath)): if len(clearpath) < 1: break - # elif math.sqrt(diffx**2+diffy**2) > 30: - # break - elif i < (len(clearpath)-1): + elif i<(len(clearpath)-1)<21: cv2.line(res, clearpath[i], clearpath[i+1], (255,0,0), 3) + elif 20 < i < (len(clearpath)-1): + cv2.rectangle(res, (0,0), (600, 400), (0,0,0)) + for j in range(20): + cv2.line(res, clearpath[-(j+1)], clearpath[-(j+2)], (255,0,0), 3) - print(path) - print(clearpath) frame_num += 1 # cnts = cv2.findContours(res, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #display the resulting frame From 8cb3025c04052b30a8a9943ac166541afca75e48 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Sat, 10 Mar 2018 13:17:15 -0500 Subject: [PATCH 11/39] stuff gets put together in main.py --- canvas.py | 14 +++++++--- fingerTrack.py | 72 +++++++++++++++++++++++++++++++++----------------- main.py | 34 ++++++++++++++++++++++++ paint.py | 33 ++++++++++++++++++++--- 4 files changed, 123 insertions(+), 30 deletions(-) diff --git a/canvas.py b/canvas.py index e79c9fbd..7f1c95bd 100644 --- a/canvas.py +++ b/canvas.py @@ -5,8 +5,8 @@ class canvas(): def __init__(self, width, height): - self.width = width - self.height = height + self.width = int(width) + self.height = int(height) self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) def set_color(self, B, G, R): @@ -19,10 +19,18 @@ def show_canvas(self): cv2.imshow('newCanvas', self.new_canvas) def save_drawing(self): - """""" + """This function allows users to save their drawing with a name of + their choice. + """ file_name = input('Please name your drawing: ') cv2.imwrite(file_name+'.jpg', self.new_canvas) + def clear(self): + """This function clears the screen. + """ + cv2.rectangle(self.new_canvas, (0, 0), (self.width, self.height), (0, 0, 0)) + + if __name__ == "__main__": canvas1 = canvas(1280, 960) canvas1.set_color(0, 0, 0) diff --git a/fingerTrack.py b/fingerTrack.py index d93bca7b..973d491e 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -16,20 +16,45 @@ def __init__(self): self.red_maskL = [np.array([0, 150, 100]), np.array([178, 150, 100])] self.red_maskH = [np.array([1, 255, 255]), np.array([180, 255, 255])] self.refreshDelay = 0 + self.colors = [] + self.dist = [] + + def map(self, x, oldL, oldH, newL, newH): + """This function maps a value from one range to a differnet range + + x: the value in the old range + oldL: the lower limit of the old range of values + oldH: the upper limit of the old range of values + newL: the lower limit of the new range of values + newH: the upper limit of the new range of values + """ + return int(((x - oldL)/(oldH-oldL))*(newH-newL)+newL) + + def brush_color(self, hue): + """This function takes in a uint8 value for hue and generate + a BGR color range """ + color = np.uint8([[[hue, 255, 255]]]) + return cv2.cvtColor(color, cv2.COLOR_HSV2BGR) def BGR2HSV(self, frame): - """This functions takes in a frame and converts it from BGR to BGR2HSV + """This functions takes in a frame and converts it from BGR + to HSV values """ return cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) def red_mask(self, frame): - """This function generates a red mask based on the frame being passed in + """This function generates a red mask based on the frame being + passed in """ mask1 = cv2.inRange(frame, self.red_maskL[0], self.red_maskH[0]) mask2 = cv2.inRange(frame, self.red_maskL[1], self.red_maskH[1]) return (mask1 | mask2) def find_center(self, mask, target): + """This function takes in a cv2 mask, find the center of the + contours in the mask, and draw a green dot at the center location + on the target frame + """ im2, contours, hierarchy = cv2.findContours(mask, 1, 2) try: if self.frame_num > self.refreshDelay or self.frame_num == 0: @@ -44,37 +69,36 @@ def find_center(self, mask, target): except IndexError: """""" - def draw(self, target): + def refine_path(self): + """This function takes evalutes every two consecutive points, + find the distance between them, and add the new point to a + list of clear path if they are not off by roughly 15 pixels. + It also takes a distance and convert it to a color to be used + when plotting the line. + """ if len(self.path) == 1: - clearpath.append(path[0]) + self.clearpath.append(self.path[0]) elif len(self.path) > 2: pair = self.path[-2] - # print(pair, pair[0], cx, pair[1], cy) diffx = abs(self.cx-pair[0]) diffy = abs(self.cy-pair[1]) distance = math.sqrt(diffx**2+diffy**2) if distance<10: + dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) + paintColor = self.brush_color(dist2hue) + self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) self.clearpath.append(pair) + + def draw(self, canvas, disappr=True): + """This function draws the lines on the canvas of the screen. + The default is that only the 20 newest points will be drawn on screen. + """ for i in range(len(self.clearpath)): if len(self.clearpath) < 1: break - elif i<(len(self.clearpath)-1)<21: - cv2.line(res, self.clearpath[i], self.clearpath[i+1], (255,0,0), 3) - elif 20 < i < (len(self.clearpath)-1): - cv2.rectangle(res, (0,0), (600, 400), (0,0,0)) + elif i<(len(self.clearpath)-1)<21 and not disappr: + cv2.line(canvas.new_canvas, self.clearpath[i], self.clearpath[i+1], self.colors[i], 3) + elif 20 < i < (len(self.clearpath)-1) and disappr: + canvas.clear() for j in range(20): - cv2.line(res, self.clearpath[-(j+1)], self.clearpath[-(j+2)], (255,0,0), 3) - # for i in range(len(self.path)): - # # TODO: Add if statements to make sure that any outliers - # # would be removed from the list or ignored when drawing - # # the linewidth - # diffx = math.abs(self.cx-self.path[-1][0]) - # print(diffx) - # diffy = math.abs(cy-path[-1][1]) - # print(diffy) - # if len(self.path) == 1: - # break - # # elif math.sqrt(diffx**2+diffy**2) > 30: - # # break - # if i < (len(self.clearpath)-1): - # cv2.line(target, self.clearpath[i], self.path[i+1], (255, 0, 0), 3) + cv2.line(canvas.new_canvas, self.clearpath[-(j+1)], self.clearpath[-(j+2)], self.colors[-(j+2)], 3) diff --git a/main.py b/main.py index c7f9e08e..9ef1382a 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,43 @@ from fingerTrack import * from canvas import * +import cv2 + def main(): """ """ + track = finger_track() + cap = cv2.VideoCapture(0) + newCanvas = canvas(cap.get(3), cap.get(4)) + disappr = True + + + while True: + ret, frame = cap.read() + frame = cv2.flip(frame,1) + cv2.imshow('original', frame) + hsv = track.BGR2HSV(frame) + redMask = track.red_mask(hsv) + mask = cv2.bilateralFilter(redMask, 10, 40, 40) + mask = cv2.blur(mask, (5, 5)) + res = cv2.bitwise_and(frame, frame, mask=redMask) + mask = cv2.blur(mask, (20, 20)) + track.find_center(mask, frame) + track.refine_path() + track.draw(newCanvas, disappr=disappr) + newCanvas.show_canvas() + + if cv2.waitKey(1) & 0xFF == ord('s'): + newCanvas.save_drawing() + break + elif cv2.waitKey(1) & 0xFF == ord('q'): + break + elif cv2.waitKey(1) & 0xFF == ord('d'): + disappr = ~disappr + elif cv2.waitKey(1) & 0xFF == ord('c'): + newCanvas.clear() + + cv2.destroyAllWindows() if __name__ == "__main__": main() diff --git a/paint.py b/paint.py index 60936467..a936e7cc 100644 --- a/paint.py +++ b/paint.py @@ -10,6 +10,26 @@ cy = 0 path = [] clearpath = [] +dist = [] +colors = [] + + +def map(x, oldL, oldH, newL, newH): + """This function maps a value from one range to a differnet range + + x: the value in the old range + oldL: the lower limit of the old range of values + oldH: the upper limit of the old range of values + newL: the lower limit of the new range of values + newH: the upper limit of the new range of values + """ + return int(((x - oldL)/(oldH-oldL))*(newH-newL)+newL) + +def brush_color(hue): + """This function takes in a uint8 value for hue and generate + a BGR color range """ + color = np.uint8([[[hue, 255, 255]]]) + return cv2.cvtColor(color, cv2.COLOR_HSV2BGR) while(True): #capture frame by frame @@ -17,7 +37,7 @@ frame = cv2.flip(frame,1) hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) #find specific color - lower_white = np.array([[0, 0, 230]]) + lower_white = np.array([0, 0, 230]) upper_white = np.array([180, 25, 255]) lower_color = np.array([0,80,50]) upper_color = np.array([20,100,100]) @@ -69,18 +89,25 @@ diffy = abs(cy-pair[1]) distance = math.sqrt(diffx**2+diffy**2) if distance<10: + dist2hue = map(distance, 0.0, 10.0, 0.0, 255.0) + paintColor = brush_color(dist2hue) + print(paintColor[0][0][0]) + colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + print(colors) clearpath.append(pair) for i in range(len(clearpath)): if len(clearpath) < 1: break elif i<(len(clearpath)-1)<21: - cv2.line(res, clearpath[i], clearpath[i+1], (255,0,0), 3) + cv2.line(res, clearpath[i], clearpath[i+1], colors[i], 3) elif 20 < i < (len(clearpath)-1): cv2.rectangle(res, (0,0), (600, 400), (0,0,0)) for j in range(20): - cv2.line(res, clearpath[-(j+1)], clearpath[-(j+2)], (255,0,0), 3) + cv2.line(res, clearpath[-(j+1)], clearpath[-(j+2)], colors[-(j+2)], 3) + + # print(dist) frame_num += 1 # cnts = cv2.findContours(res, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #display the resulting frame From 6d330fe6d36aaf2ba7237021b000dfed1735e329 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Sun, 11 Mar 2018 11:47:18 -0400 Subject: [PATCH 12/39] dev branch --- fingerTrack.py | 78 ++++++++++++++++++++++++++++++++------------------ main.py | 5 ++-- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/fingerTrack.py b/fingerTrack.py index 973d491e..775cbcc7 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -13,6 +13,7 @@ def __init__(self): self.cy = 0 self.path = [] self.clearpath = [] + self.pathlength = 20 self.red_maskL = [np.array([0, 150, 100]), np.array([178, 150, 100])] self.red_maskH = [np.array([1, 255, 255]), np.array([180, 255, 255])] self.refreshDelay = 0 @@ -50,6 +51,9 @@ def red_mask(self, frame): mask2 = cv2.inRange(frame, self.red_maskL[1], self.red_maskH[1]) return (mask1 | mask2) + def shift(self, myList, myElement): + return myList[1:] + [myElement] + def find_center(self, mask, target): """This function takes in a cv2 mask, find the center of the contours in the mask, and draw a green dot at the center location @@ -64,41 +68,59 @@ def find_center(self, mask, target): self.cx = int(M['m10'] / M['m00']) self.cy = int(M['m01'] / M['m00']) self.frame_num = 0 - self.path.append((self.cx, self.cy)) + if len(self.path) <= 1: + self.path.append((self.cx, self.cy)) + elif len(self.path) >= 2: + # Calculate the distance between the two newest point. + pair = self.path[-1] + diffx = abs(self.cx-pair[0]) + diffy = abs(self.cy-pair[1]) + distance = math.sqrt(diffx**2+diffy**2) + if distance < 10: + if len(self.path) < self.pathlength: + self.path.append((self.cx, self.cy)) + else: + self.path = self.shift(self.path, (self.cx, self.cy)) + dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) + paintColor = self.brush_color(dist2hue) + self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) except IndexError: """""" - def refine_path(self): - """This function takes evalutes every two consecutive points, - find the distance between them, and add the new point to a - list of clear path if they are not off by roughly 15 pixels. - It also takes a distance and convert it to a color to be used - when plotting the line. - """ - if len(self.path) == 1: - self.clearpath.append(self.path[0]) - elif len(self.path) > 2: - pair = self.path[-2] - diffx = abs(self.cx-pair[0]) - diffy = abs(self.cy-pair[1]) - distance = math.sqrt(diffx**2+diffy**2) - if distance<10: - dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) - paintColor = self.brush_color(dist2hue) - self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) - self.clearpath.append(pair) + # def refine_path(self): + # """This function takes evalutes every two consecutive points, + # find the distance between them, and add the new point to a + # list of clear path if they are not off by roughly 15 pixels. + # It also takes a distance and convert it to a color to be used + # when plotting the line. + # """ + # if len(self.path) == 1: + # self.clearpath.append(self.path[0]) + # elif len(self.path) > 2: + # pair = self.path[-2] + # diffx = abs(self.cx-pair[0]) + # diffy = abs(self.cy-pair[1]) + # distance = math.sqrt(diffx**2+diffy**2) + # if distance<10: + # dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) + # paintColor = self.brush_color(dist2hue) + # self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + # self.clearpath.append(pair) def draw(self, canvas, disappr=True): """This function draws the lines on the canvas of the screen. The default is that only the 20 newest points will be drawn on screen. """ - for i in range(len(self.clearpath)): - if len(self.clearpath) < 1: + for i in range(len(self.path)): + if len(self.path) <= 1: break - elif i<(len(self.clearpath)-1)<21 and not disappr: - cv2.line(canvas.new_canvas, self.clearpath[i], self.clearpath[i+1], self.colors[i], 3) - elif 20 < i < (len(self.clearpath)-1) and disappr: - canvas.clear() - for j in range(20): - cv2.line(canvas.new_canvas, self.clearpath[-(j+1)], self.clearpath[-(j+2)], self.colors[-(j+2)], 3) + else: + if i < len(self.path)-1: + cv2.line(canvas.new_canvas, self.path[i], self.path[i+1], self.colors[i], 3) + # elif i<(len(self.path)-1)<21 and not disappr: + # cv2.line(canvas.new_canvas, self.path[i], self.clearpath[i+1], self.colors[i], 3) + # elif 20 < i < (len(self.clearpath)-1) and disappr: + # canvas.clear() + # for j in range(20): + # cv2.line(canvas.new_canvas, self.clearpath[-(j+1)], self.clearpath[-(j+2)], self.colors[-(j+2)], 3) diff --git a/main.py b/main.py index 9ef1382a..396cc3b6 100644 --- a/main.py +++ b/main.py @@ -15,15 +15,16 @@ def main(): while True: ret, frame = cap.read() frame = cv2.flip(frame,1) - cv2.imshow('original', frame) + hsv = track.BGR2HSV(frame) redMask = track.red_mask(hsv) mask = cv2.bilateralFilter(redMask, 10, 40, 40) mask = cv2.blur(mask, (5, 5)) res = cv2.bitwise_and(frame, frame, mask=redMask) + cv2.imshow('original', res) mask = cv2.blur(mask, (20, 20)) track.find_center(mask, frame) - track.refine_path() + # track.refine_path() track.draw(newCanvas, disappr=disappr) newCanvas.show_canvas() From 03de6baf0fe2c8f8dd5547b31b9f494bf0c9a0cb Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Sun, 11 Mar 2018 12:19:03 -0400 Subject: [PATCH 13/39] tested stable drawing app --- fingerTrack.py | 6 +++--- main.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/fingerTrack.py b/fingerTrack.py index 973d491e..fae9e237 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -96,9 +96,9 @@ def draw(self, canvas, disappr=True): for i in range(len(self.clearpath)): if len(self.clearpath) < 1: break - elif i<(len(self.clearpath)-1)<21 and not disappr: + elif i<(len(self.clearpath)-1)<21: cv2.line(canvas.new_canvas, self.clearpath[i], self.clearpath[i+1], self.colors[i], 3) - elif 20 < i < (len(self.clearpath)-1) and disappr: - canvas.clear() + elif 20 < i < (len(self.clearpath)-1): + canvas.new_canvas = np.zeros((canvas.height, canvas.width, 3), np.uint8) for j in range(20): cv2.line(canvas.new_canvas, self.clearpath[-(j+1)], self.clearpath[-(j+2)], self.colors[-(j+2)], 3) diff --git a/main.py b/main.py index 9ef1382a..6eb6a517 100644 --- a/main.py +++ b/main.py @@ -30,13 +30,14 @@ def main(): if cv2.waitKey(1) & 0xFF == ord('s'): newCanvas.save_drawing() break - elif cv2.waitKey(1) & 0xFF == ord('q'): + if cv2.waitKey(1) & 0xFF == ord('q'): break - elif cv2.waitKey(1) & 0xFF == ord('d'): + if cv2.waitKey(1) & 0xFF == ord('d'): disappr = ~disappr - elif cv2.waitKey(1) & 0xFF == ord('c'): + if cv2.waitKey(1) & 0xFF == ord('c'): newCanvas.clear() + cap.release() cv2.destroyAllWindows() if __name__ == "__main__": From 85fd4c416d7d17774ba52c4c48a43fac3117da6c Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Sun, 11 Mar 2018 12:30:43 -0400 Subject: [PATCH 14/39] improved ways to locate outliers --- fingerTrack.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fingerTrack.py b/fingerTrack.py index 775cbcc7..6b9c7870 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -13,7 +13,7 @@ def __init__(self): self.cy = 0 self.path = [] self.clearpath = [] - self.pathlength = 20 + self.pathlength = 10 self.red_maskL = [np.array([0, 150, 100]), np.array([178, 150, 100])] self.red_maskH = [np.array([1, 255, 255]), np.array([180, 255, 255])] self.refreshDelay = 0 @@ -64,7 +64,7 @@ def find_center(self, mask, target): if self.frame_num > self.refreshDelay or self.frame_num == 0: cnt = contours[0] M = cv2.moments(cnt) - print(M['m10'] / M['m00']) + #print(M['m10'] / M['m00']) self.cx = int(M['m10'] / M['m00']) self.cy = int(M['m01'] / M['m00']) self.frame_num = 0 @@ -76,7 +76,8 @@ def find_center(self, mask, target): diffx = abs(self.cx-pair[0]) diffy = abs(self.cy-pair[1]) distance = math.sqrt(diffx**2+diffy**2) - if distance < 10: + if distance < 100: + print('Far enough') if len(self.path) < self.pathlength: self.path.append((self.cx, self.cy)) else: @@ -84,9 +85,11 @@ def find_center(self, mask, target): dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) paintColor = self.brush_color(dist2hue) self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + print(paintColor, pair) cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) except IndexError: """""" + print('IndexError happened') # def refine_path(self): # """This function takes evalutes every two consecutive points, @@ -112,11 +115,12 @@ def draw(self, canvas, disappr=True): """This function draws the lines on the canvas of the screen. The default is that only the 20 newest points will be drawn on screen. """ + canvas.new_canvas = np.zeros((canvas.height, canvas.width, 3), np.uint8) for i in range(len(self.path)): if len(self.path) <= 1: break else: - if i < len(self.path)-1: + if i < len(self.path)-2: cv2.line(canvas.new_canvas, self.path[i], self.path[i+1], self.colors[i], 3) # elif i<(len(self.path)-1)<21 and not disappr: # cv2.line(canvas.new_canvas, self.path[i], self.clearpath[i+1], self.colors[i], 3) From 05032a99c6ef60054bf7d8bcc9a6794667fb5a71 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Sun, 11 Mar 2018 20:35:25 -0400 Subject: [PATCH 15/39] Intersection checking --- fingerTrack.py | 44 +++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/fingerTrack.py b/fingerTrack.py index 6b9c7870..671e6859 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -76,8 +76,8 @@ def find_center(self, mask, target): diffx = abs(self.cx-pair[0]) diffy = abs(self.cy-pair[1]) distance = math.sqrt(diffx**2+diffy**2) - if distance < 100: - print('Far enough') + if distance < 150: + # print('Far enough') if len(self.path) < self.pathlength: self.path.append((self.cx, self.cy)) else: @@ -87,29 +87,10 @@ def find_center(self, mask, target): self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) print(paintColor, pair) cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) + self.notFound = False except IndexError: """""" - print('IndexError happened') - - # def refine_path(self): - # """This function takes evalutes every two consecutive points, - # find the distance between them, and add the new point to a - # list of clear path if they are not off by roughly 15 pixels. - # It also takes a distance and convert it to a color to be used - # when plotting the line. - # """ - # if len(self.path) == 1: - # self.clearpath.append(self.path[0]) - # elif len(self.path) > 2: - # pair = self.path[-2] - # diffx = abs(self.cx-pair[0]) - # diffy = abs(self.cy-pair[1]) - # distance = math.sqrt(diffx**2+diffy**2) - # if distance<10: - # dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) - # paintColor = self.brush_color(dist2hue) - # self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) - # self.clearpath.append(pair) + self.notFound = True def draw(self, canvas, disappr=True): """This function draws the lines on the canvas of the screen. @@ -122,9 +103,14 @@ def draw(self, canvas, disappr=True): else: if i < len(self.path)-2: cv2.line(canvas.new_canvas, self.path[i], self.path[i+1], self.colors[i], 3) - # elif i<(len(self.path)-1)<21 and not disappr: - # cv2.line(canvas.new_canvas, self.path[i], self.clearpath[i+1], self.colors[i], 3) - # elif 20 < i < (len(self.clearpath)-1) and disappr: - # canvas.clear() - # for j in range(20): - # cv2.line(canvas.new_canvas, self.clearpath[-(j+1)], self.clearpath[-(j+2)], self.colors[-(j+2)], 3) + + if len(self.path) > 4: + def det(p1, p2, p3, p4): + deltaA = p1[0] - p2[0] + deltaB = p1[1] - p2[1] + deltaC = p3[0] - p4[0] + deltaD = p3[1] - p4[1] + return deltaA * deltaD - deltaB * deltaC + div = det(self.path[-1], self.path[-2], self.path[-3], self.path[-4]) + if div != 0 and not self.notFound: + print('the two line intersects!!!') From d5ed339a92e5ad8bda7ce46a76dd86080357137c Mon Sep 17 00:00:00 2001 From: Tweir195 <31592190+Tweir195@users.noreply.github.com> Date: Sun, 11 Mar 2018 21:17:05 -0400 Subject: [PATCH 16/39] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7322eb77..60ebced7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # InteractiveProgramming This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. +You need to install OpenCV for this project. Run this line to install: +$ pip install opencv-python + ### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From b0cfb1ce088b2f7e42ff813ef7d415be6dc412bb Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Tue, 13 Mar 2018 12:37:03 -0400 Subject: [PATCH 17/39] Started adding the rectangles, need x and y lists --- canvas.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/canvas.py b/canvas.py index 7f1c95bd..1c1858eb 100644 --- a/canvas.py +++ b/canvas.py @@ -1,5 +1,6 @@ import numpy as np import cv2 +import random class canvas(): @@ -8,6 +9,10 @@ def __init__(self, width, height): self.width = int(width) self.height = int(height) self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) + self.randx = [] + self.randy = [] + #white,red, green, blue, yellow, purple, orange + self.colorlist = [(255,255,255), (0,0,255), (0,255,0), (255,0,0), (0,255,255), (255,0,188), (0,15,255)] def set_color(self, B, G, R): self.color = (B, G, R) @@ -30,6 +35,11 @@ def clear(self): """ cv2.rectangle(self.new_canvas, (0, 0), (self.width, self.height), (0, 0, 0)) + def rectangle(self): + xpos = random.choice(self.randx) + ypos = rand.choice(self.randy) + color = rand.choice(self.colorlist) + cv2.rectangle(self.new_canvas, (xpos, ypos), (10,10), color) if __name__ == "__main__": canvas1 = canvas(1280, 960) From 04fef8b299b6137326764ce60e90a2ec9f8574a7 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Tue, 13 Mar 2018 12:37:47 -0400 Subject: [PATCH 18/39] update colors --- canvas.py | 2 +- fingerTrack.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/canvas.py b/canvas.py index 7f1c95bd..c04cc29b 100644 --- a/canvas.py +++ b/canvas.py @@ -28,7 +28,7 @@ def save_drawing(self): def clear(self): """This function clears the screen. """ - cv2.rectangle(self.new_canvas, (0, 0), (self.width, self.height), (0, 0, 0)) + canvas.new_canvas = np.zeros((canvas.height, canvas.width, 3), np.uint8) if __name__ == "__main__": diff --git a/fingerTrack.py b/fingerTrack.py index 671e6859..8706fd67 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -82,10 +82,13 @@ def find_center(self, mask, target): self.path.append((self.cx, self.cy)) else: self.path = self.shift(self.path, (self.cx, self.cy)) - dist2hue = self.map(distance, 0.0, 10.0, 0.0, 255.0) + dist2hue = self.map(distance, 0.0, 150.0, 0.0, 255.0) paintColor = self.brush_color(dist2hue) - self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) - print(paintColor, pair) + if len(self.colors) < self.pathlength: + self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + else: + self.colors = self.shift(self.colors, (int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + print(self.colors) cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) self.notFound = False except IndexError: @@ -113,4 +116,5 @@ def det(p1, p2, p3, p4): return deltaA * deltaD - deltaB * deltaC div = det(self.path[-1], self.path[-2], self.path[-3], self.path[-4]) if div != 0 and not self.notFound: - print('the two line intersects!!!') + """""" + # print('the two line intersects!!!') From 4c194aa87bbfbe417c2fb007f8cdc7c423f1e17a Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Tue, 13 Mar 2018 13:03:34 -0400 Subject: [PATCH 19/39] added x and y for rectangle --- canvas.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/canvas.py b/canvas.py index 0bdce1de..9e20efbb 100644 --- a/canvas.py +++ b/canvas.py @@ -9,10 +9,12 @@ def __init__(self, width, height): self.width = int(width) self.height = int(height) self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) - self.randx = [] - self.randy = [] + self.randx = np.linrange(10,580) + self.randy = np.linrange(10,380) #white,red, green, blue, yellow, purple, orange self.colorlist = [(255,255,255), (0,0,255), (0,255,0), (255,0,0), (0,255,255), (255,0,188), (0,15,255)] + self.points = 0 + self.value = 10 def set_color(self, B, G, R): self.color = (B, G, R) @@ -36,11 +38,16 @@ def clear(self): canvas.new_canvas = np.zeros((canvas.height, canvas.width, 3), np.uint8) def rectangle(self): - xpos = random.choice(self.randx) - ypos = rand.choice(self.randy) - color = rand.choice(self.colorlist) + self.xpos = random.choice(self.randx) + self.ypos = random.choice(self.randy) + color = random.choice(self.colorlist) cv2.rectangle(self.new_canvas, (xpos, ypos), (10,10), color) + def in_rect(self,point): + if self.xpos<=point[0]<=self.xpos and self.ypos<=point[1]<=self.ypos + self.points += self.value + self.clear() + if __name__ == "__main__": canvas1 = canvas(1280, 960) canvas1.set_color(0, 0, 0) From ce9d63f136e70bcb1b7fcb0f96459149c5220150 Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Tue, 13 Mar 2018 13:21:17 -0400 Subject: [PATCH 20/39] Put in rectangles, they are popping up when not wanted --- canvas.py | 18 +++++++++--------- main.py | 15 +++++++++++---- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/canvas.py b/canvas.py index 9e20efbb..9dc9c583 100644 --- a/canvas.py +++ b/canvas.py @@ -9,8 +9,8 @@ def __init__(self, width, height): self.width = int(width) self.height = int(height) self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) - self.randx = np.linrange(10,580) - self.randy = np.linrange(10,380) + self.randx = np.linspace(10,580) + self.randy = np.linspace(10,380) #white,red, green, blue, yellow, purple, orange self.colorlist = [(255,255,255), (0,0,255), (0,255,0), (255,0,0), (0,255,255), (255,0,188), (0,15,255)] self.points = 0 @@ -35,18 +35,18 @@ def save_drawing(self): def clear(self): """This function clears the screen. """ - canvas.new_canvas = np.zeros((canvas.height, canvas.width, 3), np.uint8) + canvas.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) def rectangle(self): - self.xpos = random.choice(self.randx) - self.ypos = random.choice(self.randy) + self.xpos = int(random.choice(self.randx)) + self.ypos = int(random.choice(self.randy)) color = random.choice(self.colorlist) - cv2.rectangle(self.new_canvas, (xpos, ypos), (10,10), color) + cv2.rectangle(self.new_canvas, (self.xpos, self.ypos), (self.xpos+20,self.ypos+20), color,-1) - def in_rect(self,point): - if self.xpos<=point[0]<=self.xpos and self.ypos<=point[1]<=self.ypos + def in_rect(self,pointx,pointy): + if self.xpos Date: Tue, 13 Mar 2018 13:57:25 -0400 Subject: [PATCH 21/39] Fixed the rectangles, it works, need to make outlines of rectangles thicker --- canvas.py | 19 ++++++++++++++----- fingerTrack.py | 2 +- main.py | 13 ++++++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/canvas.py b/canvas.py index 9dc9c583..847186c8 100644 --- a/canvas.py +++ b/canvas.py @@ -13,8 +13,10 @@ def __init__(self, width, height): self.randy = np.linspace(10,380) #white,red, green, blue, yellow, purple, orange self.colorlist = [(255,255,255), (0,0,255), (0,255,0), (255,0,0), (0,255,255), (255,0,188), (0,15,255)] + self.boxsize = 30 self.points = 0 self.value = 10 + self.run = False def set_color(self, B, G, R): self.color = (B, G, R) @@ -37,17 +39,24 @@ def clear(self): """ canvas.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) - def rectangle(self): + def make_rect(self): self.xpos = int(random.choice(self.randx)) self.ypos = int(random.choice(self.randy)) - color = random.choice(self.colorlist) - cv2.rectangle(self.new_canvas, (self.xpos, self.ypos), (self.xpos+20,self.ypos+20), color,-1) + self.color = random.choice(self.colorlist) + + def show_rect(self): + cv2.rectangle(self.new_canvas, (self.xpos, self.ypos), (self.xpos+self.boxsize,self.ypos+self.boxsize), self.color) + self.run = True def in_rect(self,pointx,pointy): - if self.xpos Date: Tue, 13 Mar 2018 23:59:27 -0400 Subject: [PATCH 22/39] Added optparser for mods; setted up timer for countdown --- canvas.py | 5 ++-- fingerTrack.py | 14 +++++++---- main.py | 63 +++++++++++++++++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/canvas.py b/canvas.py index 847186c8..9a888ed6 100644 --- a/canvas.py +++ b/canvas.py @@ -54,15 +54,14 @@ def in_rect(self,pointx,pointy): self.make_rect() return True - def addpoints(self): + def addpoints(self, track): self.points += self.value + track.pathlength += 1 if __name__ == "__main__": canvas1 = canvas(1280, 960) canvas1.set_color(0, 0, 0) canvas1.set_bgColor() - # cam = cv2.VideoCapture(0) - # print(cam.get(3), cam.get(4)) while True: canvas1.show_canvas() if cv2.waitKey(1) & 0xFF == ord('s'): diff --git a/fingerTrack.py b/fingerTrack.py index 4d118639..2b76c5de 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -54,7 +54,7 @@ def red_mask(self, frame): def shift(self, myList, myElement): return myList[1:] + [myElement] - def find_center(self, mask, target): + def find_center(self, mask, target, disappr=True): """This function takes in a cv2 mask, find the center of the contours in the mask, and draw a green dot at the center location on the target frame @@ -81,13 +81,19 @@ def find_center(self, mask, target): if len(self.path) < self.pathlength: self.path.append((self.cx, self.cy)) else: - self.path = self.shift(self.path, (self.cx, self.cy)) + if disappr: + self.path = self.shift(self.path, (self.cx, self.cy)) + else: + self.path.append((self.cx, self.cy)) dist2hue = self.map(distance, 0.0, 150.0, 0.0, 255.0) paintColor = self.brush_color(dist2hue) if len(self.colors) < self.pathlength: self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) else: - self.colors = self.shift(self.colors, (int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + if disappr: + self.colors = self.shift(self.colors, (int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) + else: + self.colors.append((int(paintColor[0][0][0]), int(paintColor[0][0][1]), int(paintColor[0][0][2]))) # print(self.colors) cv2.circle(target, (self.cx, self.cy), 2, (0, 255, 0), -1) self.notFound = False @@ -95,7 +101,7 @@ def find_center(self, mask, target): """""" self.notFound = True - def draw(self, canvas, disappr=True): + def draw(self, canvas): """This function draws the lines on the canvas of the screen. The default is that only the 20 newest points will be drawn on screen. """ diff --git a/main.py b/main.py index 7bcdbac4..09137f61 100644 --- a/main.py +++ b/main.py @@ -1,20 +1,45 @@ from fingerTrack import * from canvas import * import cv2 +from optparse import OptionParser +import time + +def init_opts(): + parser = OptionParser() + parser.add_option("-d", action="store_false", + dest="disappr", default=True, + help="To run the program in the drawing mode") + parser.add_option("-t", action="store_true", + dest="disappr", default=True, + help="To run the program in the tailing mode where the lines disappear after a period of time") + parser.add_option("-g", action="store_true", + dest="game", default=False, + help="To run the program in the gaming mode where you try to hit boxes") + parser.add_option("-l", "--length", action="store", type='int', + dest="length", default=3, + help="The starting length of the line") + options, args = parser.parse_args() + return options, args def main(): """ """ + start = time.time() + options, args = init_opts() track = finger_track() cap = cv2.VideoCapture(0) newCanvas = canvas(cap.get(3), cap.get(4)) - disappr = True - + disappr = options.disappr + track.pathlength = options.length while True: + if time.time() - start > 1: + print(time.time() - start) + start = time.time() + ret, frame = cap.read() - frame = cv2.flip(frame,1) + frame = cv2.flip(frame, 1) hsv = track.BGR2HSV(frame) redMask = track.red_mask(hsv) @@ -23,22 +48,23 @@ def main(): res = cv2.bitwise_and(frame, frame, mask=redMask) cv2.imshow('original', res) mask = cv2.blur(mask, (20, 20)) - track.find_center(mask, frame) + track.find_center(mask, frame, disappr=disappr) # track.refine_path() - track.draw(newCanvas, disappr=disappr) + track.draw(newCanvas) # newCanvas.rectangle() - print(newCanvas.points, newCanvas.run) - if newCanvas.points == 0: - if newCanvas.run == False: - newCanvas.make_rect() + if options.game: + print(newCanvas.points, newCanvas.run) + if newCanvas.points == 0: + if newCanvas.run == False: + newCanvas.make_rect() + newCanvas.show_rect() + flag = newCanvas.in_rect(track.cx, track.cy) + if flag == True: + newCanvas.addpoints(track) + newCanvas.clear() + newCanvas.show_rect() newCanvas.show_rect() - flag = newCanvas.in_rect(track.cx,track.cy) - if flag == True: - newCanvas.addpoints() - newCanvas.clear() - newCanvas.show_rect() - newCanvas.show_rect() newCanvas.show_canvas() @@ -48,11 +74,16 @@ def main(): newCanvas.save_drawing() break if cv2.waitKey(1) & 0xFF == ord('d'): - disappr = ~disappr + if disappr: + disappr = False + else: + disappr = True + print(disappr) if cv2.waitKey(1) & 0xFF == ord('c'): newCanvas.clear() cv2.destroyAllWindows() if __name__ == "__main__": + main() From 09aa8319bdb21a7b7ee2bae99362c946f96edf2d Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Wed, 14 Mar 2018 14:43:02 -0400 Subject: [PATCH 23/39] Gaming interface --- main.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 09137f61..6240d4cd 100644 --- a/main.py +++ b/main.py @@ -25,19 +25,31 @@ def init_opts(): def main(): """ """ - start = time.time() + + font = cv2.FONT_HERSHEY_SIMPLEX options, args = init_opts() track = finger_track() cap = cv2.VideoCapture(0) newCanvas = canvas(cap.get(3), cap.get(4)) disappr = options.disappr track.pathlength = options.length - + game_time = 5 + current_time = 1 + start = time.time() while True: - if time.time() - start > 1: - print(time.time() - start) + if time.time() - start > 1 and options.game: + current_time += 1 start = time.time() + if current_time == game_time+1: + while True: + cv2.putText(newCanvas.new_canvas, 'Yay!!!', (int(newCanvas.width/2-100), int(newCanvas.height/2)), font, 3, (255, 0, 0), 2) + cv2.putText(newCanvas.new_canvas, 'Your final score is:', (int(newCanvas.width/2-300), int(newCanvas.height/2+50)), font, 2, (0, 0, 255), 2) + cv2.putText(newCanvas.new_canvas, str(newCanvas.points)+'!!!', (int(newCanvas.width/2-75), int(newCanvas.height/2+50)+75), font, 3, (0, 255, 0), 2) + newCanvas.show_canvas() + if cv2.waitKey(1) & 0xFF == ord('q'): + break + break ret, frame = cap.read() frame = cv2.flip(frame, 1) @@ -54,7 +66,7 @@ def main(): # newCanvas.rectangle() if options.game: - print(newCanvas.points, newCanvas.run) + # print(newCanvas.points, newCanvas.run) if newCanvas.points == 0: if newCanvas.run == False: newCanvas.make_rect() @@ -65,6 +77,7 @@ def main(): newCanvas.clear() newCanvas.show_rect() newCanvas.show_rect() + cv2.putText(newCanvas.new_canvas, 'Time left: '+str(game_time-current_time), (0, 15), font, .5, (255, 255, 255), 1) newCanvas.show_canvas() @@ -78,7 +91,7 @@ def main(): disappr = False else: disappr = True - print(disappr) + # print(disappr) if cv2.waitKey(1) & 0xFF == ord('c'): newCanvas.clear() From 25f1cdc21226da4a99e0910aeffbf0a0ab6f34e8 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Wed, 14 Mar 2018 17:51:38 -0400 Subject: [PATCH 24/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60ebced7..1a183b81 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,6 @@ This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. You need to install OpenCV for this project. Run this line to install: -$ pip install opencv-python +`$ pip install opencv-python` ### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From 3b81a7a8abd9885412639651af60b5940aec5c56 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Wed, 14 Mar 2018 22:56:57 -0400 Subject: [PATCH 25/39] Update README.md --- README.md | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a183b81..213654af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,36 @@ -# InteractiveProgramming +# Interactive Programming This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. -You need to install OpenCV for this project. Run this line to install: -`$ pip install opencv-python` +You need to install OpenCV for this project. Run this line to install: `$ pip install opencv-python` + +## Project Overview + +To run the program, please either download zip or type in `git clone https://github.com/Tweir195/InteractiveProgramming.git` in the command line. To run the program, please type `python main.py` in the command line. + +Our program allows for three different modes, which you can specify through the few additional commands in the command line: + +`-t`: The trailing mode where the line you control is of fixed length, so older points after a certain number of new points are drawn will disappear. This is the default mode in which the program will run. + +`-g`: The gaming mode in which you try to hit colored boxes and get scores and added length to your line upon hittng the boxes. + +`-l` or `--length`: An integer following this command will specify the starting length of the line in the trailing mode and the gaming mode. The default starting length is 3. + +`-d` or `--draw`: The drawing mode where the line you control will stay on the screen as long as you haven't quit. + +When the program is running, there are several keyboar d interactions that you can use: Key `q` would allow you to quit the program; key `s` would allow you to save the current canvas; key `t` would allow you to toggle trailing ON or OFF. + +## Results + + +## Implementation: St +Our program include two classes: `canvas` and `fingerTrack`. Whereas the `canvas` class create a canvas to be drawn on and update the canvas, `fingerTrack` performs color tracking and actual drawing onto the canvas. + +The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to th list. + +At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. + +## Reflection + + ### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From a9addc157441dc980b84697a793740435d2ad6f7 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Wed, 14 Mar 2018 23:27:28 -0400 Subject: [PATCH 26/39] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 213654af..e16a2b0a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ This is the base repo for the interactive programming project for Software Desig You need to install OpenCV for this project. Run this line to install: `$ pip install opencv-python` ## Project Overview +In this interactive programming project, we created a program that uses OpenCV to track a red object and makes up a drawing app by drawing in a canvas where the red object has been. +#### To run the program To run the program, please either download zip or type in `git clone https://github.com/Tweir195/InteractiveProgramming.git` in the command line. To run the program, please type `python main.py` in the command line. Our program allows for three different modes, which you can specify through the few additional commands in the command line: @@ -22,13 +24,15 @@ When the program is running, there are several keyboar d interactions that you c ## Results -## Implementation: St +## Implementation: still needs a UML Our program include two classes: `canvas` and `fingerTrack`. Whereas the `canvas` class create a canvas to be drawn on and update the canvas, `fingerTrack` performs color tracking and actual drawing onto the canvas. The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to th list. At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. +The gaming mode is just a step up from the simple trailing mode. When the gaming mode is enabled, the canvas class would place a randomly colorred rectangle at a random location inside canvas. Upon a point falling inside the drawn rectangle, users will be given ten points, and the number of coordinates that a list is allowed to have before shifting out the first element is increased by 1. The current rectanlge disappears while a new one shows up at another randomly chosen location. AT the start of the program, the current time of the program is also recorded. Whenever one second elapses, the counterdown in the game mode will decrease by one. When the countdown finishes, the program will freeze gaming and display the scores that the user receives in this round. + ## Reflection From c0e79f4d280d15b651a82cbbf06c62d2f04070a2 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Thu, 15 Mar 2018 15:47:21 -0400 Subject: [PATCH 27/39] Update canvas.py --- canvas.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/canvas.py b/canvas.py index 9a888ed6..15b52f27 100644 --- a/canvas.py +++ b/canvas.py @@ -6,12 +6,24 @@ class canvas(): def __init__(self, width, height): + """Initizes a canvas class with following attributes + + width: the width of the drawing canvas given in pixels + height: the height of the drawing canvas given in pixels + new_canvas: a numpy array of zeros with depth of 3 + randx: a range of x pixel value to choose from + randy: a range of y pixel value to choose from + colorlist: white,red, green, blue, yellow, purple, orange to randomly choose from + boxsize: the size of the box appearing in the gaming mode + points: the score a user has earned + value: the score given to a user upon hitting one rectangle + run: a boolean that makes sure boxes appear and disappear accordingly + """ self.width = int(width) self.height = int(height) self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) self.randx = np.linspace(10,580) self.randy = np.linspace(10,380) - #white,red, green, blue, yellow, purple, orange self.colorlist = [(255,255,255), (0,0,255), (0,255,0), (255,0,0), (0,255,255), (255,0,188), (0,15,255)] self.boxsize = 30 self.points = 0 @@ -19,12 +31,18 @@ def __init__(self, width, height): self.run = False def set_color(self, B, G, R): + """Stores the BGR value + """ self.color = (B, G, R) def set_bgColor(self): + """Applies the BGR value to the drawing canvas + """ self.new_canvas[:, :] = self.color def show_canvas(self): + """Displays a canvas on the screen + """ cv2.imshow('newCanvas', self.new_canvas) def save_drawing(self): @@ -45,6 +63,8 @@ def make_rect(self): self.color = random.choice(self.colorlist) def show_rect(self): + """Draw a rectangle in the + """ cv2.rectangle(self.new_canvas, (self.xpos, self.ypos), (self.xpos+self.boxsize,self.ypos+self.boxsize), self.color) self.run = True From 0acbcf1c874d574d0f181b8911906936d2090ed3 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Thu, 15 Mar 2018 15:50:57 -0400 Subject: [PATCH 28/39] Update main.py --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index a505769f..89f79258 100644 --- a/main.py +++ b/main.py @@ -77,7 +77,7 @@ def main(): newCanvas.clear() newCanvas.show_rect() newCanvas.show_rect() - cv2.putText(newCanvas.new_canvas, 'Time left: '+str(game_time-current_time), (0, 15), font, .5, (255, 255, 255), 1) + cv2.putText(newCanvas.new_canvas, 'Time left: '+str(game_time-current_time), (0, 15), font, .5, (255, 255, 255), 1) newCanvas.show_canvas() From 3213c6d8e1bb54925ebcc1bc2cfa7fcc14f0d88f Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Thu, 15 Mar 2018 15:51:46 -0400 Subject: [PATCH 29/39] update readme.py --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index a505769f..89f79258 100644 --- a/main.py +++ b/main.py @@ -77,7 +77,7 @@ def main(): newCanvas.clear() newCanvas.show_rect() newCanvas.show_rect() - cv2.putText(newCanvas.new_canvas, 'Time left: '+str(game_time-current_time), (0, 15), font, .5, (255, 255, 255), 1) + cv2.putText(newCanvas.new_canvas, 'Time left: '+str(game_time-current_time), (0, 15), font, .5, (255, 255, 255), 1) newCanvas.show_canvas() From fbce65b369f17c8ab73ced60e0b76b8e03ac56f3 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Thu, 15 Mar 2018 16:22:29 -0400 Subject: [PATCH 30/39] a larger canvas for drawing and trailing; sample images of our program --- canvas.py | 24 +++++++++++++++--------- drawing.jpg | Bin 0 -> 73421 bytes fingerTrack.py | 6 +++--- gaming.jpg | Bin 0 -> 17941 bytes main.py | 9 ++++++--- 5 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 drawing.jpg create mode 100644 gaming.jpg diff --git a/canvas.py b/canvas.py index 15b52f27..8706c837 100644 --- a/canvas.py +++ b/canvas.py @@ -5,9 +5,9 @@ class canvas(): - def __init__(self, width, height): + def __init__(self, width, height, scaler): """Initizes a canvas class with following attributes - + width: the width of the drawing canvas given in pixels height: the height of the drawing canvas given in pixels new_canvas: a numpy array of zeros with depth of 3 @@ -19,11 +19,12 @@ def __init__(self, width, height): value: the score given to a user upon hitting one rectangle run: a boolean that makes sure boxes appear and disappear accordingly """ - self.width = int(width) - self.height = int(height) + self.screen_scaler = scaler + self.width = int(width) * self.screen_scaler + self.height = int(height) * self.screen_scaler self.new_canvas = np.zeros((self.height, self.width, 3), np.uint8) - self.randx = np.linspace(10,580) - self.randy = np.linspace(10,380) + self.randx = np.linspace(10,580*self.screen_scaler) + self.randy = np.linspace(10,380*self.screen_scaler) self.colorlist = [(255,255,255), (0,0,255), (0,255,0), (255,0,0), (0,255,255), (255,0,188), (0,15,255)] self.boxsize = 30 self.points = 0 @@ -63,12 +64,17 @@ def make_rect(self): self.color = random.choice(self.colorlist) def show_rect(self): - """Draw a rectangle in the + """Draws a rectangle in the gaming mode. """ - cv2.rectangle(self.new_canvas, (self.xpos, self.ypos), (self.xpos+self.boxsize,self.ypos+self.boxsize), self.color) + cv2.rectangle(self.new_canvas, (self.xpos, self.ypos), (self.xpos+self.boxsize,self.ypos+self.boxsize), self.color, 3) self.run = True - def in_rect(self,pointx,pointy): + def in_rect(self, pointx, pointy): + """Decides whether a point is within a rectangle + + pointx: x pixel position of the point + pointy: y pixel position of the point + """ if self.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

OW z$zzsvd1B5lo9j^T3|2bLy&z>vH~Qh&Z>F$YC6|n?zPx2|IDOw1qwO}_{6^e(8%YPa ziKCZ3FV;&(7Ln+QX?VBFND!vnR$TzA9k%?_2aUUEy5bbJBw3Yv-zI5pP72j0y(21j zmPoBvj3e+3HY2wW)v|x@_3oaDley~faj;kuYE3UP(zctKk(oj)&{wQ!O8~JKBY{pU zym&2Ml86BgvYbN7B&%-&URnU79pS8JUr7N?3ABw#?`HOg-B#7*NWAoYd)2D7>1~@@ z<-`nOsuxXC+8}n3eEo$M+x0yDB1IRb7w6V}Sis4*KCp|Z8In}ezxB}aRLt4N_*pN- zAB<QBh=EO^yDL%KW%l@Qt4pO=d1IK1~mb z7RmTurUgeBT#BOjpQZ%>iqHf`H~vhE&u7bS^aIo4ZrGW#d6!nXzE#$an``g-$g;_r z*qg=t%#kwIlc#cx0tTLs|CFr$F$9MS5Isqz2Xuv^?FQ`rbO8p&Qwl*tf73aP)dpRZ zu5_A?!Bva4zvz&V{k9|~@&L@c|7skvJ zklc}V(-Dva4?%-)N(_dR1-b~y*AjbTRuZj;8Y+MM(}0w?Rtc95PeX+tj*zs~_|th1 z9NYB?6}5!pb+K!M=l-U9ob2~lr^Lkg+KgaKwL4XMyhVz93enC3XYa0X^$N~5{@FHj zv#=r5>xoVpO)rR4@!bu8VD{g%VRl1nQ;RF2VoPB7;37VJ`f2$Btje)vAU#`(2ln0n0=`Nn_2RTc+@MDqz%+2~+kaXX zj+bp;`)RL}@miW})4C?&7znOEsX37eCwcQPKdt8UGY*gClEuhcHwy(S`HRUXuL-Z> z%7C=Qyg2CpUUKr^vK{|zug?Qr7PF%k+2G9EUZnJ8k*-Wc2PTPF0oBM^9(o?cx%&3= z)2^}m{oRcGvPQ3T#CrZhR?--OQy~oGjt)3R^&u%3WtTK3}B z1JA1@J^1UD25z+L@Li1U2WZNpmL_>R$X>RQ1g!0urQzG5AAD2CDsk>E_q+>=It{Hn zXJB&H*7ij6s_SB>=)e64;}#suH_L6_|P9gKctkqatL}eQ+5?i6D6QLn<{#JU=SY zIweK^k!~3y)UWGu5yA;fS#nl3fO`FiHCi8fPTi}KLy07YoT3}&;JC({_j-_);mBzyd{4m0R+US zA-dsY?&UbZI*cemYR|p5s(6 z+w2O6T+X@3h<;@M3yiVdKG4VuhbQoGzaV9oyf76>#hNCSV83k6v{I?h62>hX6w~~y zW)JqG_S$6Oz(lRh-XU)c5z3YzQ7sD4^Q9OMVqr<{(9M`BUG&kbdAEJ1?;o_xVrOjr zrPbaUnyO&T_5G<*wsFI#-RN4_#^j;QmhezkkrGg{dyfu#)OGcG|dp zxFap@3Zw_{TR;xM_`U_e_AKLTc`ln@l?V3@3tznc`;-LKhN4R9B$y3+Tdajds%bi1u5u-8*3&LMT5xv9e<%#(5&J9V)+ddh zeEPl8S5$cYpHlt;rWnvvQ$@NkwPSLD8AZ^@^eqOTYm10OLQ{@BAM&M*<1UYbpYJdy z@uykXOO72826c`>6$Z4jL1UWV*apy^W}K>Yg{~ z^4BG*tc|LvT&SwHKy|4&Au@O#4C>j7!R(^XrW(%0 zN@Mp1zD7A?WhJXH53gOwwzF$w0WYksHnSd=b_P^+Gm%}t4ivY=>-b7FHMhmJ7U=CX z`fF$@^X7wim425VRzHTnrazIkk6)2{+%7HsK+x&J?e7)GN90$C_9rw|+6>wm z4+kx?;RVqz5Tpk?(q9s-WVn z=aZ)Q1;B9*9}xe)0_6M`K<9t;_4MR>fWg8sxkJ%g&V0VD|Fd?xc(;~%zvut$0C@Y^ zy|bq;YXdlnPymRb;e$j;bQy5+Qe0`-Sds7cZzbK81bH1!uDJ zIzJV6JJyJb94j)ka@`mvFkgIyzGgRZY2%y$3p=Xzox@bd|I^-;MKzUe+gLUT2muw9 zHb^5vAP6WZ1R_!p5K*zz2ndv-^oW2KAkDd=B1#iF>AadkI*}%&5Q88_dZZPQzD*Dc zkU*jk5|W&}?R}s3=Z*XG#(RU`ljfYU_S|c(b=KN*xe(&}zwLQWe#b|H|CUx3W<^GX zs-&=|=^tn;IJ~^z!WX@-KtF|k_o_5eHwCZ!G4y87efw}ko54*<|HW0@5E>R&1exPD zR>uc5u3k~tMaBHyQtAREK{;BdN^AVne z5KGf8fr(T%OrP}^>Rtsr(aQJH&1IQ{%|YstmDcsuElP8D2Q_566qzI-J|L~fwPxVn zc)M4&)Rhq$OwMo)Y7Ryk9sQ!~Y|}P2_G$BC&d6>vSC52o+w*_%vcrb`NY;&XbvAQOarBqc!+g20{`WM z^xSjFw2M`rwUQ=Kn|+Zld=(b1AnbZqE%#s| z?^WbpVd2uBZzDWSF^P=CO$XAtF2(MQXw^*c4-XD4c)|%iVWqd8 z9X&A^VB%Mqk>ap4@U|~BcVPx+*NrThRuMBRa>q2{@e(TqwambvllJ8m344ft^Zcx% zC}mLH79hurwyEjYjl9D8aqjuZfZdh8Qc4XaXTIM~*2c5aw&As+WvKNh#cUhfACugW zZozIZIB`^jz%bQCh}3VL4M0kL3**I@k%G=-=Q#rrqBn=uD%28VbjMzrd-q${p@qrL zi!%K5F&7@nsK+fE4DfN{djOq{!pKvgq=h^yLQEf-y}gp|M>t+1 zzyH8e4QAI1ywA^2c-IR2m=Pv^!=s-8-!7PeH$i{?&)(0V=a67X&dFu&!O*7rG)OY~ z({o*lf_K6gRPW%QqaDTP@T|^;VFmtX?`ubcYhJ$#0b+&Cj%nvE811k&HHh*(eaNNY zeU3|It@F~&=?W|rUBEPWl6%tpZ%w(r(i~U91d%jHF2d14`p7zmTsOf9YE~fFJ`Yv` z>B?4B5h4%JSvw6cLVT?T0jzVF2Yl}(0LT#Z9S@?BMhDg?{P5Blfp(q<@rh*60|wVN zTF~Bl33qqLg3~--Al@%T?!!S&2hgv{X0?EVk0}w1?<64CSL;zL^2k~dB834p-e8g7 z*p=ut7++sW7M=E)c&AeT_t0N2i9Fz*1k>&(>m@&HQ{zv9oQKZR$Fr-PjFsW=s100-8$@u zO=)raVS+>1L54zK#rYh+3WuU0-aLrZRI`Vl<~YkkaB zC-LMV2Q%ONJWbEHndrp}tiVMIeOzLgLVlFfdpXYWxl2U&sf+BFQW@dW^0CHmZ8~H^ zsxB7A(Y#3yK5Xd&q9+2mtC>v6YR$rDGBFc|N;i0%X)#^P;~!H-RRFye`!9(@Z;+x{ef`_KkE*)8|A}R;FNj99(XZ?+i-KiJ+-r@YKt6G1T zq&hF;nVgeYKVhblaMWX0R_BGzZ`H(T+xe}+(2LRji|_5(YYcMSQvNfn)9u10z0!eh zzq~FEyVB^_t2qZoM)UC}+S+FBO1JmKl^Z`s^M06i{YX;p@FI4iyJ~#`k9}&pNO)5- zqO;U<(Q#OV_^RP|R@TwrvoVjq+d&OWFbBlP;a;}?Nono>+HVM@KJHW^^L4)8AJMA! zIF;OZ^QOd(kdM_569O^0o;EwSq&TSKb*=>`l_nVOld&ahy?DnydJ1EhRuoqx-?WnI z5Y=tf-7<6RqMw{n!1fC#=R=weDQi^2h++3yibZY@H1!6kO$*UZmHql3Ohy*Yy13$X zM{X}aINTpQFL&3#_6pVRmqUtnc$+#ir_$_$+2_fjK;92 zx_!l3*y>d3_d4K`>?p8TlLuCf)u61YlWn2w!t-fH-HCY&8Lc$HYFZ*h2{;McaZiMp zSYMf=O-&((=%^rW0NaGsQ@ISEA-5`^XM>_8!Xh3&PPo|{yEap70=fCv2cl8(kJpXE z?A#om=qk+zix7Ig<@~^IZD#Ml_{6#k2MMdt0B;ls3!$%5@5R4DSD{vQc#lcL5XF2V zbbYcynB~fY7V;)*+QHOwW)Wzpl?nhq*ds#Z{svr{{4o)N=ju*OCjgftqaN$Sukycv zNjE(q_u@s(uxY@;>Yb%w6k8vwZ9iT=%}&44D^n&XH}R=2knqRqoww1Ts9c{99D`)! zZ6K6bEG%I)x9|zDD?~RYHPm3qW57L{F^wm^x-&_u+B6HVPFe_x^$Ewa3|v=$K_Eb1 zB#96iN4Pv|6nlG!bM%a~T643cE!j_RuXTQEkIxJHPhtYg%Z$u-R*zn@V|ooZZ+g+! z|NE0SE~vjC7DIms-{Q`8;hHdTNolqNP+w(!57y4ZJayE8-}>M^(MR1*y4f|U_t^D1 zOyklK3doPUIj2cBHTN1wdtfzZUwuEdAy>D_OP8&7G@Wiq)vQd+KR%?nCGOHMn=!$- z{k{W_%D!1B3LXTIFxzg+UyUo2P9E-OsDr}4Tvxpm>=zhYL<3Kti|&@=Z5 zGIuaO_u~1eYjqNQV}yg`K7bGO4rK1R(05%&oJkpdwLCi#x)}Y0CQ$q*U7W~i-1_?a zJr*B9B6A*uC`<}u(3dE^OOuwuV!a!b?aZ&n7YCz}hZ+vooo)<2w{#-T1yP-sEWY1m zA5MkFEaqRcjXj3*JM*Zt^iW%5_{(Ix5H9itBtvFGxeJ`?@k}OedN8MJM(cq-mJk{w zX?W;&742)@GnbPb`=7*GZrO?E=t~q|{l4#Nrc%GtL!AB_P7nw4jNGv-wiV-7qHR5z z$*Ax~iF0|%1X;&`TVZ#6a0mL)`2vbX45-lWfd?j694(Xp8&CWk+?)(}`QI;=bkV?S zv(BQ{^=Ulcd3QlPUSOUEOKb-83W!e66%82JZ*{qxYbin;9|C8-1b?{?lWawZb?J{$ z{WzX2)Bw77rvAjO9H)I>=%w*=h}>IXESVS_w-IDW^nG@rM#bZQAzssaV&pv~OsZ`LQV;vOXzUNw*}nCuyfH)9~AwX{q6{9Y`yu{`DtxeJ*BlaXFmMk02>Wsq)&XEU1r%(C7aSos z5Z8bC263g3B7`!z3~Vkdy1sBvCByn(^*!xiL^hbKappOCC>jy{5%t!K zY2~F-#D(LTkrXqbV?`QtouaAPNo@2UEzKerzH2WB;3dLG&i)q&u1>M77n&U`S+&}ZsR zGhJN&deeJ_IzSe9FtSzxR1}NC(wGMW1dmk5wOUg?1*zRN?whq3M2Saw0!Ka%)mg!q za83@sF-oh>c<&wiOXEK9j?dHa(M)=gOMIGnhmqXPTt>EWxOc1E_m&x)BKJoNY8Te= z_B8ZJ#R!acDsx-p-yoO!8jg=&_P0(nX-6A<_+sstdh-=xN&R1Suclz9sj5$VSD@V#h~gZaOfKUq1$yd3|Dc&Vl8KO&AE?!xNJz+$QBdqZh^N0 zS<_vBcC3m7@=xnYBa?Ha5$7}YsBKLm#4fj3bLvgJTsnO2^kV4cNK&mMzay8|Jq=b< zsi53G{6IEe7w$Grc4-OSWmP6PUhU(?Kx**f{L3^fB_G+2FlLWC8lK(T2xXin(6fK*jZN zkNs2pK-=VPnm|dB?`1n{XQ3c4^z_K^$n(_zBzt%DHy2=MAv`it9v@yX6lO-B$TsgR zO|dGzG#8q9B=1eKW`aYw#`Q1HKAM_dlWJ>F72vF+6IHg-w>u9V^qB$dtv*%sz#u1+ zW39OSDsZ`Ga5f6iI1+TS#3VyK&CpD49@rk5C>9UN)fN*fdBM&KI1xgP&~b|px?a|R zTGb`yfTyJm1ZYd??s`4^9FI)RRJi?;HfqycMdm#m!CQ zzJKlk0mIG@9xsTiF0r_|ZA4h&A^2+)d&hf_)#PHp%80)NcNZ)5gQyU*j3MRw!;K~^ z{h)?7U;UD~3-2*4+3tN|JYH7mQ0wc)Y^<8ji-fJ9#zhtwxBSes@Ih=d(8D$ko{&h$ z>Tzw?Zex_bJTXZp@>IscTw<@t?VIw>f9uwU5SZ1-{cz`KQm<=ldOFUxk?>1$Nl_vp!fqqDe?nsO)a?^b>5 z2!>!}#DR?1v+jSbf#yG}OKsYC-m@Dv*uc&P1~xFTfq@MSY+zsm0~;9Fz`zCuHZZV( Pfej4&f5(8BX!ySX*{2u~ literal 0 HcmV?d00001 diff --git a/fingerTrack.py b/fingerTrack.py index 34df2d75..55ab225f 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -54,7 +54,7 @@ def red_mask(self, frame): def shift(self, myList, myElement): return myList[1:] + [myElement] - def find_center(self, mask, target, disappr=True): + def find_center(self, mask, target, canvas, disappr=True): """This function takes in a cv2 mask, find the center of the contours in the mask, and draw a green dot at the center location on the target frame @@ -65,8 +65,8 @@ def find_center(self, mask, target, disappr=True): cnt = contours[0] M = cv2.moments(cnt) #print(M['m10'] / M['m00']) - self.cx = int(M['m10'] / M['m00']) - self.cy = int(M['m01'] / M['m00']) + self.cx = int(M['m10'] / M['m00']) * canvas.screen_scaler + self.cy = int(M['m01'] / M['m00']) * canvas.screen_scaler self.frame_num = 0 if len(self.path) <= 1: self.path.append((self.cx, self.cy)) diff --git a/gaming.jpg b/gaming.jpg new file mode 100644 index 0000000000000000000000000000000000000000..093f6c608a230590e5b3bce447fd276c309c0274 GIT binary patch literal 17941 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 Pef$p${5b|7$f^GYXn?=6 literal 0 HcmV?d00001 diff --git a/main.py b/main.py index 89f79258..d7a0a6bb 100644 --- a/main.py +++ b/main.py @@ -30,10 +30,13 @@ def main(): options, args = init_opts() track = finger_track() cap = cv2.VideoCapture(0) - newCanvas = canvas(cap.get(3), cap.get(4)) + scaler = 2 + if options.game: + scaler = 1 + newCanvas = canvas(cap.get(3), cap.get(4), scaler) disappr = options.disappr track.pathlength = options.length - game_time = 5 + game_time = 30 current_time = 1 start = time.time() while True: @@ -60,7 +63,7 @@ def main(): res = cv2.bitwise_and(frame, frame, mask=redMask) cv2.imshow('original', res) mask = cv2.blur(mask, (20, 20)) - track.find_center(mask, frame, disappr=disappr) + track.find_center(mask, frame, newCanvas, disappr=disappr) # track.refine_path() track.draw(newCanvas) From 41b52db0036a628c82dda183795c194bf9c0890d Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Thu, 15 Mar 2018 16:44:58 -0400 Subject: [PATCH 31/39] Updating Read Me, pictures may not work, fill out reflection --- README.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e16a2b0a..cff4109c 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,6 @@ This is the base repo for the interactive programming project for Software Desig You need to install OpenCV for this project. Run this line to install: `$ pip install opencv-python` -## Project Overview -In this interactive programming project, we created a program that uses OpenCV to track a red object and makes up a drawing app by drawing in a canvas where the red object has been. - #### To run the program To run the program, please either download zip or type in `git clone https://github.com/Tweir195/InteractiveProgramming.git` in the command line. To run the program, please type `python main.py` in the command line. @@ -19,22 +16,30 @@ Our program allows for three different modes, which you can specify through the `-d` or `--draw`: The drawing mode where the line you control will stay on the screen as long as you haven't quit. -When the program is running, there are several keyboar d interactions that you can use: Key `q` would allow you to quit the program; key `s` would allow you to save the current canvas; key `t` would allow you to toggle trailing ON or OFF. +When the program is running, there are several keyboard interactions that you can use: Key `q` would allow you to quit the program; key `s` would allow you to save the current canvas; key `t` would allow you to toggle trailing ON or OFF. + +## Project Overview +In this interactive programming project, we created a program that uses OpenCV to track a red object and makes up a drawing app by drawing in a canvas where the red object has been. There are a few modes that you can choose from: a game, a drawing canvas, and a canvas with a disappearing line. ## Results +The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to the list. +![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/drawing.jpg) +At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. -## Implementation: still needs a UML -Our program include two classes: `canvas` and `fingerTrack`. Whereas the `canvas` class create a canvas to be drawn on and update the canvas, `fingerTrack` performs color tracking and actual drawing onto the canvas. +![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/gaming.jpg) +The gaming mode is just a step up from the simple trailing mode. When the gaming mode is enabled, the canvas class would place a randomly colored rectangle at a random location inside canvas. Upon a point falling inside the drawn rectangle, users will be given ten points, and the tail of the line will be made longer. The current rectangle disappears while a new one shows up at another randomly chosen location. At the start of the program, the current time of the program is also recorded. When a countdown from 30 finishes, the program will freeze gaming and display the scores that the user receives in this round.Whenever one second elapses, the countdown in the game mode will decrease by one. -The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to th list. +## Implementation: still needs a UML +Our program includes two classes: `canvas` and `fingerTrack`. The `canvas` class creates a canvas to be drawn on. The initialization of `canvas` involves three input parameters: the width of the canvas, the height, and the scaler to enlarge the canvas by. The width and the height are based off the size of the camera frame. The scaler is always 2 except in the gaming mode where a scaler of 1 is applied to allow for better accuracy when playing the game. The `fingerTrack` class performs color tracking and actual drawing on the canvas. Each location of the points to be drawn are also scaled with the scaler in the `canvas` class. It also takes the `canvas` class as an input parameter when drawing new points and lines. -At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. +The number of coordinates that a list is allowed to have before shifting out the first element is increased by 1. +Whenever one second elapses, the countdown in the game mode will decrease by one. -The gaming mode is just a step up from the simple trailing mode. When the gaming mode is enabled, the canvas class would place a randomly colorred rectangle at a random location inside canvas. Upon a point falling inside the drawn rectangle, users will be given ten points, and the number of coordinates that a list is allowed to have before shifting out the first element is increased by 1. The current rectanlge disappears while a new one shows up at another randomly chosen location. AT the start of the program, the current time of the program is also recorded. Whenever one second elapses, the counterdown in the game mode will decrease by one. When the countdown finishes, the program will freeze gaming and display the scores that the user receives in this round. +In our beginning planning of the project, after we had decided on using OpenCV to make a drawing platform, we had a few options. We thought about trying to make a game similar to the snake game, but the snake was controlled by the web-cam on the computer. We also thought about just making a canvas on the screen that could be drawn on by the web-cam, and have the color be controlled by the speed of movement. We were unsure about how easily we would be able to make the snake game, and decided on making the drawing canvas. However, we finished this with plenty of time, and expanded into a game in addition to the drawing canvas. ## Reflection - +One thing that would have helped us a lot was having a more appropriately scoped project. We ended up doing a cool project. ### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From e1174fd44a757c986b5cf048ff046f9965acd6d9 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Thu, 15 Mar 2018 17:21:07 -0400 Subject: [PATCH 32/39] delete redundant if statement --- main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main.py b/main.py index d7a0a6bb..132536e9 100644 --- a/main.py +++ b/main.py @@ -89,17 +89,13 @@ def main(): if cv2.waitKey(1) & 0xFF == ord('s'): newCanvas.save_drawing() break - if cv2.waitKey(1) & 0xFF == ord('d'): if disappr: disappr = False else: disappr = True - if cv2.waitKey(1) & 0xFF == ord('q'): - break if cv2.waitKey(1) & 0xFF == ord('d'): disappr = ~disappr - if cv2.waitKey(1) & 0xFF == ord('c'): newCanvas.clear() From 67ecefdf71b629e5c4d0143edb33c7da2fb988fb Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Thu, 15 Mar 2018 17:28:56 -0400 Subject: [PATCH 33/39] Need to finish implementation and reflection --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cff4109c..1325ed3e 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,11 @@ In this interactive programming project, we created a program that uses OpenCV t The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to the list. ![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/drawing.jpg) + At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. ![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/gaming.jpg) + The gaming mode is just a step up from the simple trailing mode. When the gaming mode is enabled, the canvas class would place a randomly colored rectangle at a random location inside canvas. Upon a point falling inside the drawn rectangle, users will be given ten points, and the tail of the line will be made longer. The current rectangle disappears while a new one shows up at another randomly chosen location. At the start of the program, the current time of the program is also recorded. When a countdown from 30 finishes, the program will freeze gaming and display the scores that the user receives in this round.Whenever one second elapses, the countdown in the game mode will decrease by one. ## Implementation: still needs a UML @@ -39,7 +41,8 @@ Whenever one second elapses, the countdown in the game mode will decrease by one In our beginning planning of the project, after we had decided on using OpenCV to make a drawing platform, we had a few options. We thought about trying to make a game similar to the snake game, but the snake was controlled by the web-cam on the computer. We also thought about just making a canvas on the screen that could be drawn on by the web-cam, and have the color be controlled by the speed of movement. We were unsure about how easily we would be able to make the snake game, and decided on making the drawing canvas. However, we finished this with plenty of time, and expanded into a game in addition to the drawing canvas. ## Reflection -One thing that would have helped us a lot was having a more appropriately scoped project. We ended up doing a cool project. +One thing that would have helped us a lot was having a more appropriately scoped project that takes better advantage of classes. We ended up doing a cool project, but because we started in an easier project, we had to keep finding ways to expand. This made us experiment with the code, without realizing we should have made a separate class for each mode. +One of the good things about the experience is that we both pair programmed and divide-and-conquer in the process. Our approach in dividing and conquer were based on the next tasks that need to get done instead of which files or classes specifically so that both us have a good understanding of the program as a whole. We were able to move along in the project quite steadily, not feeling stressed about not feeling our project. ### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From 3622ce8d8810d65f1eb710eb7f28e606551de39c Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Thu, 15 Mar 2018 21:15:08 -0400 Subject: [PATCH 34/39] UML Diagram --- UML_Project_4.jpg | Bin 0 -> 10586 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 UML_Project_4.jpg diff --git a/UML_Project_4.jpg b/UML_Project_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d7adb127f68ddb5ebff7f906ff60e1936e1412d GIT binary patch literal 10586 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 bvpLbk|BKk^Q3q_W#=%iJSg6DY^iQ literal 0 HcmV?d00001 From f32610b1dda15177382f8dcbbdcf0df9d5fc1a33 Mon Sep 17 00:00:00 2001 From: QingmuDeng Date: Thu, 15 Mar 2018 21:41:23 -0400 Subject: [PATCH 35/39] docstrings for finger track --- fingerTrack.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fingerTrack.py b/fingerTrack.py index 55ab225f..25926023 100644 --- a/fingerTrack.py +++ b/fingerTrack.py @@ -6,7 +6,7 @@ class finger_track(): def __init__(self): - """ + """Initiates the finger tracking class """ self.frame_num = 0 self.cx = 0 @@ -28,6 +28,12 @@ def map(self, x, oldL, oldH, newL, newH): oldH: the upper limit of the old range of values newL: the lower limit of the new range of values newH: the upper limit of the new range of values + + doctest: + >>> map(1, 0, 10, 0, 200) + 20 + >>> map(50, 0, 150, 0, 255) + 85 """ return int(((x - oldL)/(oldH-oldL))*(newH-newL)+newL) @@ -52,6 +58,11 @@ def red_mask(self, frame): return (mask1 | mask2) def shift(self, myList, myElement): + """Shift out the first element in this list and append the new element to the end + + >>> shift([1, 2, 3], 5) + [2, 3, 5] + """ return myList[1:] + [myElement] def find_center(self, mask, target, canvas, disappr=True): From d21c57334182187b741c6dbe4d0136c09b3970b4 Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Thu, 15 Mar 2018 21:41:31 -0400 Subject: [PATCH 36/39] added docstrings --- main.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 132536e9..cdf00749 100644 --- a/main.py +++ b/main.py @@ -5,6 +5,10 @@ import time def init_opts(): + """ + This function runs the different modes of our program with input in the + command line + """ parser = OptionParser() parser.add_option("-d", action="store_false", dest="disappr", default=True, @@ -23,13 +27,16 @@ def init_opts(): def main(): - """ + """ This function puts together the methods from classes in other files to + run the program """ font = cv2.FONT_HERSHEY_SIMPLEX options, args = init_opts() track = finger_track() cap = cv2.VideoCapture(0) + # this makes the canvas larger for all modes except for the game because the + # game needs a smaller canvas to be less frustrating to play scaler = 2 if options.game: scaler = 1 @@ -43,7 +50,8 @@ def main(): if time.time() - start > 1 and options.game: current_time += 1 start = time.time() - + # Display the page that shows your score when the time is up until you + # press the q key if current_time == game_time+1: while True: cv2.putText(newCanvas.new_canvas, 'Yay!!!', (int(newCanvas.width/2-100), int(newCanvas.height/2)), font, 3, (255, 0, 0), 2) @@ -56,6 +64,7 @@ def main(): ret, frame = cap.read() frame = cv2.flip(frame, 1) + # This section tracks the color red that is in the frame hsv = track.BGR2HSV(frame) redMask = track.red_mask(hsv) mask = cv2.bilateralFilter(redMask, 10, 40, 40) @@ -64,12 +73,10 @@ def main(): cv2.imshow('original', res) mask = cv2.blur(mask, (20, 20)) track.find_center(mask, frame, newCanvas, disappr=disappr) - # track.refine_path() track.draw(newCanvas) - # newCanvas.rectangle() + # This section runs the game option if it was selected if options.game: - # print(newCanvas.points, newCanvas.run) if newCanvas.points == 0: if newCanvas.run == False: newCanvas.make_rect() @@ -94,8 +101,6 @@ def main(): disappr = False else: disappr = True - if cv2.waitKey(1) & 0xFF == ord('d'): - disappr = ~disappr if cv2.waitKey(1) & 0xFF == ord('c'): newCanvas.clear() @@ -103,5 +108,4 @@ def main(): cv2.destroyAllWindows() if __name__ == "__main__": - main() From 43c31c703cb9d8c354457a217be9a3337bff8d3f Mon Sep 17 00:00:00 2001 From: Tweir195 Date: Thu, 15 Mar 2018 21:51:02 -0400 Subject: [PATCH 37/39] Finished project report --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1325ed3e..eaffb172 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,16 @@ The gaming mode is just a step up from the simple trailing mode. When the gaming ## Implementation: still needs a UML Our program includes two classes: `canvas` and `fingerTrack`. The `canvas` class creates a canvas to be drawn on. The initialization of `canvas` involves three input parameters: the width of the canvas, the height, and the scaler to enlarge the canvas by. The width and the height are based off the size of the camera frame. The scaler is always 2 except in the gaming mode where a scaler of 1 is applied to allow for better accuracy when playing the game. The `fingerTrack` class performs color tracking and actual drawing on the canvas. Each location of the points to be drawn are also scaled with the scaler in the `canvas` class. It also takes the `canvas` class as an input parameter when drawing new points and lines. -The number of coordinates that a list is allowed to have before shifting out the first element is increased by 1. -Whenever one second elapses, the countdown in the game mode will decrease by one. +One of our most important data structures is a list. We use lists to store such data as the coordinates of the center of the contours and the colors we are using for the lines between two consecutive points. We chose lists because they are mutable and have built-in operations like `append`. As a result, instead of using a data type to store a large number of points that have existed in the past, we used lists so that every time a point comes in, we can simply shift out the first one and append the new element to the end of the list, thus making a big save on memory. In our beginning planning of the project, after we had decided on using OpenCV to make a drawing platform, we had a few options. We thought about trying to make a game similar to the snake game, but the snake was controlled by the web-cam on the computer. We also thought about just making a canvas on the screen that could be drawn on by the web-cam, and have the color be controlled by the speed of movement. We were unsure about how easily we would be able to make the snake game, and decided on making the drawing canvas. However, we finished this with plenty of time, and expanded into a game in addition to the drawing canvas. +UML diagram: +![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/UML_Project_4.jpg) + ## Reflection -One thing that would have helped us a lot was having a more appropriately scoped project that takes better advantage of classes. We ended up doing a cool project, but because we started in an easier project, we had to keep finding ways to expand. This made us experiment with the code, without realizing we should have made a separate class for each mode. +One thing that would have helped us a lot was having a more appropriately scoped project that takes better advantage of classes. We ended up doing a cool project, but because we started in an easier project, we had to keep finding ways to expand. This made us experiment with the code, without realizing we should have made a separate class for each mode. We could improve it by optimizing it so that there is less lag time and also so that it is better at picking up only the item that we want (this will avoid the jagged lines that can happen when there is interference). -One of the good things about the experience is that we both pair programmed and divide-and-conquer in the process. Our approach in dividing and conquer were based on the next tasks that need to get done instead of which files or classes specifically so that both us have a good understanding of the program as a whole. We were able to move along in the project quite steadily, not feeling stressed about not feeling our project. +One of the good things about the experience is that we used both pair programming and divide-and-conquer in the process. Our approach in dividing and conquer was based on the next tasks that need to get done instead of which files or classes specifically so that both us have a good understanding of the program as a whole. We were able to move along in the project quite steadily, not feeling stressed about not finishing our project. ### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). From fcecf8a8178523cba7105f780c9d7701368b485d Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Thu, 15 Mar 2018 21:53:53 -0400 Subject: [PATCH 38/39] Create ProjectReflection.md --- ProjectReflection.md | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 ProjectReflection.md diff --git a/ProjectReflection.md b/ProjectReflection.md new file mode 100644 index 00000000..53a539cd --- /dev/null +++ b/ProjectReflection.md @@ -0,0 +1,49 @@ +# Interactive Programming Project Reflection + +This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. + +You need to install OpenCV for this project. Run this line to install: `$ pip install opencv-python` + +#### To run the program +To run the program, please either download zip or type in `git clone https://github.com/Tweir195/InteractiveProgramming.git` in the command line. To run the program, please type `python main.py` in the command line. + +Our program allows for three different modes, which you can specify through the few additional commands in the command line: + +`-t`: The trailing mode where the line you control is of fixed length, so older points after a certain number of new points are drawn will disappear. This is the default mode in which the program will run. + +`-g`: The gaming mode in which you try to hit colored boxes and get scores and added length to your line upon hittng the boxes. + +`-l` or `--length`: An integer following this command will specify the starting length of the line in the trailing mode and the gaming mode. The default starting length is 3. + +`-d` or `--draw`: The drawing mode where the line you control will stay on the screen as long as you haven't quit. + +When the program is running, there are several keyboard interactions that you can use: Key `q` would allow you to quit the program; key `s` would allow you to save the current canvas; key `t` would allow you to toggle trailing ON or OFF. + +## Project Overview +In this interactive programming project, we created a program that uses OpenCV to track a red object and makes up a drawing app by drawing in a canvas where the red object has been. There are a few modes that you can choose from: a game, a drawing canvas, and a canvas with a disappearing line. + +## Results +The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to the list. + +![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/drawing.jpg) + +At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. + +![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/gaming.jpg) + +The gaming mode is just a step up from the simple trailing mode. When the gaming mode is enabled, the canvas class would place a randomly colored rectangle at a random location inside canvas. Upon a point falling inside the drawn rectangle, users will be given ten points, and the tail of the line will be made longer. The current rectangle disappears while a new one shows up at another randomly chosen location. At the start of the program, the current time of the program is also recorded. When a countdown from 30 finishes, the program will freeze gaming and display the scores that the user receives in this round.Whenever one second elapses, the countdown in the game mode will decrease by one. + +## Implementation: still needs a UML +Our program includes two classes: `canvas` and `fingerTrack`. The `canvas` class creates a canvas to be drawn on. The initialization of `canvas` involves three input parameters: the width of the canvas, the height, and the scaler to enlarge the canvas by. The width and the height are based off the size of the camera frame. The scaler is always 2 except in the gaming mode where a scaler of 1 is applied to allow for better accuracy when playing the game. The `fingerTrack` class performs color tracking and actual drawing on the canvas. Each location of the points to be drawn are also scaled with the scaler in the `canvas` class. It also takes the `canvas` class as an input parameter when drawing new points and lines. + +One of our most important data structures is a list. We use lists to store such data as the coordinates of the center of the contours and the colors we are using for the lines between two consecutive points. We chose lists because they are mutable and have built-in operations like `append`. As a result, instead of using a data type to store a large number of points that have existed in the past, we used lists so that every time a point comes in, we can simply shift out the first one and append the new element to the end of the list, thus making a big save on memory. + +In our beginning planning of the project, after we had decided on using OpenCV to make a drawing platform, we had a few options. We thought about trying to make a game similar to the snake game, but the snake was controlled by the web-cam on the computer. We also thought about just making a canvas on the screen that could be drawn on by the web-cam, and have the color be controlled by the speed of movement. We were unsure about how easily we would be able to make the snake game, and decided on making the drawing canvas. However, we finished this with plenty of time, and expanded into a game in addition to the drawing canvas. + +UML diagram: +![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/UML_Project_4.jpg) + +## Reflection +One thing that would have helped us a lot was having a more appropriately scoped project that takes better advantage of classes. We ended up doing a cool project, but because we started in an easier project, we had to keep finding ways to expand. This made us experiment with the code, without realizing we should have made a separate class for each mode. We could improve it by optimizing it so that there is less lag time and also so that it is better at picking up only the item that we want (this will avoid the jagged lines that can happen when there is interference). + +One of the good things about the experience is that we used both pair programming and divide-and-conquer in the process. Our approach in dividing and conquer was based on the next tasks that need to get done instead of which files or classes specifically so that both us have a good understanding of the program as a whole. We were able to move along in the project quite steadily, not feeling stressed about not finishing our project. From 8ea6bae0e6b5b1c6a6b4fc7a54b9b7411fe81ea8 Mon Sep 17 00:00:00 2001 From: Josh Deng <31523537+QingmuDeng@users.noreply.github.com> Date: Thu, 15 Mar 2018 21:55:24 -0400 Subject: [PATCH 39/39] Update README.md --- README.md | 37 ++++++------------------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index eaffb172..7f67e55a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ +### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md). +### Our project reflection can be found [here](https://github.com/Tweir195/InteractiveProgramming/blob/master/ProjectReflection.md). + +You need to install OpenCV for this project. Run this line to install: `$ pip install opencv-python` + # Interactive Programming + This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. You need to install OpenCV for this project. Run this line to install: `$ pip install opencv-python` @@ -17,34 +23,3 @@ Our program allows for three different modes, which you can specify through the `-d` or `--draw`: The drawing mode where the line you control will stay on the screen as long as you haven't quit. When the program is running, there are several keyboard interactions that you can use: Key `q` would allow you to quit the program; key `s` would allow you to save the current canvas; key `t` would allow you to toggle trailing ON or OFF. - -## Project Overview -In this interactive programming project, we created a program that uses OpenCV to track a red object and makes up a drawing app by drawing in a canvas where the red object has been. There are a few modes that you can choose from: a game, a drawing canvas, and a canvas with a disappearing line. - -## Results -The most basic function of our program is to draw a tailing line. Our program would first filter out the color red in the whole camera frame. After identifying the contour in the filtered red frame, the program records the coordinate of the center of contours as a point to draw lines with. In the end, we would have a list of such points. By drawing a line between every two consecutive points in the list, we would end up with the trajectory of the red object in the frame. The way we maintain the list is by appending new points to the list until the designated length has been reached and then just shifting out the first element in the list and appending the new point to the list. - -![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/drawing.jpg) - -At the same time, there's the option to make all the points stay on the canvas. To implement this, the only slight variation is that we would keep appending the coordinates to the list of all the points instead of shifting out the first one every time. - -![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/gaming.jpg) - -The gaming mode is just a step up from the simple trailing mode. When the gaming mode is enabled, the canvas class would place a randomly colored rectangle at a random location inside canvas. Upon a point falling inside the drawn rectangle, users will be given ten points, and the tail of the line will be made longer. The current rectangle disappears while a new one shows up at another randomly chosen location. At the start of the program, the current time of the program is also recorded. When a countdown from 30 finishes, the program will freeze gaming and display the scores that the user receives in this round.Whenever one second elapses, the countdown in the game mode will decrease by one. - -## Implementation: still needs a UML -Our program includes two classes: `canvas` and `fingerTrack`. The `canvas` class creates a canvas to be drawn on. The initialization of `canvas` involves three input parameters: the width of the canvas, the height, and the scaler to enlarge the canvas by. The width and the height are based off the size of the camera frame. The scaler is always 2 except in the gaming mode where a scaler of 1 is applied to allow for better accuracy when playing the game. The `fingerTrack` class performs color tracking and actual drawing on the canvas. Each location of the points to be drawn are also scaled with the scaler in the `canvas` class. It also takes the `canvas` class as an input parameter when drawing new points and lines. - -One of our most important data structures is a list. We use lists to store such data as the coordinates of the center of the contours and the colors we are using for the lines between two consecutive points. We chose lists because they are mutable and have built-in operations like `append`. As a result, instead of using a data type to store a large number of points that have existed in the past, we used lists so that every time a point comes in, we can simply shift out the first one and append the new element to the end of the list, thus making a big save on memory. - -In our beginning planning of the project, after we had decided on using OpenCV to make a drawing platform, we had a few options. We thought about trying to make a game similar to the snake game, but the snake was controlled by the web-cam on the computer. We also thought about just making a canvas on the screen that could be drawn on by the web-cam, and have the color be controlled by the speed of movement. We were unsure about how easily we would be able to make the snake game, and decided on making the drawing canvas. However, we finished this with plenty of time, and expanded into a game in addition to the drawing canvas. - -UML diagram: -![alt text](https://github.com/Tweir195/InteractiveProgramming/blob/master/UML_Project_4.jpg) - -## Reflection -One thing that would have helped us a lot was having a more appropriately scoped project that takes better advantage of classes. We ended up doing a cool project, but because we started in an easier project, we had to keep finding ways to expand. This made us experiment with the code, without realizing we should have made a separate class for each mode. We could improve it by optimizing it so that there is less lag time and also so that it is better at picking up only the item that we want (this will avoid the jagged lines that can happen when there is interference). - -One of the good things about the experience is that we used both pair programming and divide-and-conquer in the process. Our approach in dividing and conquer was based on the next tasks that need to get done instead of which files or classes specifically so that both us have a good understanding of the program as a whole. We were able to move along in the project quite steadily, not feeling stressed about not finishing our project. - -### Our initial project proposal can be found [here](https://github.com/QingmuDeng/InteractiveProgramming/blob/master/Project%20Proposal.md).