forked from CodingEZ/Automated-Garden
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
916 additions
and
352 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import time | ||
|
||
class ArduinoControl(): | ||
|
||
def __init__(self): | ||
'''Calibration and set-up''' | ||
|
||
self.unit_x = 180 # 1 x-stepper motor turn = unit_x pixels | ||
self.unit_y = 180 # same for y | ||
self.screen_x = ? # stepper motor turn per 1 screen width | ||
self.screen_y = ? # vice versa | ||
|
||
self.arduino = serial.Serial('COM3', 9600, timeout=.1) # set up the serial port | ||
time.sleep(3) # allow Arduino to reset | ||
|
||
def wait(self): | ||
try: | ||
byteNum = int((self.arduino.read()).decode()) | ||
except: | ||
byteNum = -1 | ||
|
||
while byteNum != 0: | ||
try: | ||
byteNum = int((self.arduino.read()).decode()) | ||
except: | ||
byteNum = -1 | ||
|
||
def arduino_move(self, right, down): | ||
self.arduino.write(b'0') # haven't established yet, we need a list of commands and give each a number | ||
try: | ||
byteNum = int((self.arduino.read()).decode()) | ||
except: | ||
byteNum = -1 | ||
|
||
while byteNum != 1: | ||
try: | ||
byteNum = int((self.arduino.read()).decode()) | ||
except: | ||
byteNum = -1 | ||
|
||
right = str(right) | ||
down = str(down) | ||
for i in range(3-len(right)): | ||
right = '0' + right | ||
for j in range(3-len(down)): | ||
down = '0' + down | ||
|
||
for char in right: | ||
self.arduino.write(char.encode('utf-8')) # need the bytes of the char | ||
for char2 in down: | ||
self.arduino.write(char2.encode('utf-8')) # need the bytes of the char | ||
|
||
self.wait() | ||
|
||
def arduino_water(self, timeLength=10): | ||
'''Note: time length is in seconds.''' | ||
waterStart = time.time() | ||
while time.time() < waterStart + timeLength: | ||
self.arduino.write(b'2') | ||
self.wait() | ||
|
||
def water_cycle(self): | ||
'''Cycle through plant locations and water plants.''' | ||
for location in self.plantLocations: | ||
self.arduino_move(location) | ||
self.arduino_water() | ||
self.lastWater = time.time() | ||
|
||
''' | ||
while True: | ||
if (control.lastWater == None) or (time.time() - control.lastWater > 300): | ||
control.water_cycle() | ||
control.find_weeds() | ||
control.kill_weeds() | ||
if time.time() - control.lastWater < 300: | ||
''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import numpy as np | ||
import cv2 | ||
import ImageEdit6 as ImageEdit # version 4 contains polygon detection | ||
import ImageGrab2 as ImageGrab | ||
import GripEdit3 as GripEdit # version 3 only | ||
import ImageHelp | ||
|
||
class Detector(): | ||
|
||
def __init__(self): | ||
'''Calibration and set-up''' | ||
self.plantLocations = [(), (), (), ()] | ||
self.numPlants = len(self.plantLocations) | ||
self.lastWater = None | ||
self.img = self.image_grab() | ||
self.imgEdit = GripEdit.filter(self.img) | ||
self.editor = ImageEdit.Editor(self.imgEdit) | ||
self.thresholdBrightness = .4 # .6 is the default value | ||
self.weeds = [] | ||
self.weedFactor = 1/100 # size of weeds in comparison to plant | ||
|
||
# Initialized later | ||
self.regions = None | ||
self.plant = None | ||
self.startSize = None | ||
self.finalImg = None | ||
self.largeRegions = None | ||
|
||
def image_grab(self): | ||
img = ImageGrab.grab(0) # built-in camera number = 0 | ||
# attached camera number = 1 | ||
#img = cv2.imread('Capture\\1521224317.jpg', 1) | ||
return img | ||
|
||
def find_plant(self): | ||
'''Finds the largest object and designates as the plant. | ||
Currently the most inefficient code.''' | ||
maxPixel = self.editor.find_max() | ||
while True: | ||
locations = self.editor.max_locations(maxPixel, self.thresholdBrightness) | ||
self.regions = self.editor.get_all_regions(locations) | ||
|
||
# Find the largest region. In the case of a tie, lower the brightness threshold. | ||
(largestRegion, self.startSize) = self.editor.obtain_largest_region(self.regions) | ||
if len(largestRegion) == 1: | ||
break | ||
else: | ||
self.thresholdBrightness -= .01 | ||
self.plant = largestRegion[0] # set the largest region as the plant | ||
|
||
def find_weeds(self): | ||
'''Determine if an object is a plant or a weed. Currently, the plant is removed, and everything | ||
else is designated as a weed.''' | ||
plantFound = False | ||
plantIndex = None | ||
self.largeRegions = self.editor.keep_large_regions(self.regions, self.startSize * self.weedFactor) | ||
for index in range(len(self.largeRegions)): | ||
region = self.largeRegions[index] | ||
newLocation = self.editor.find_centroid(region) | ||
if not plantFound and ImageHelp.equalArray(region, self.plant): | ||
print("Location of plant:", newLocation) | ||
plantFound = True | ||
plantIndex = index | ||
else: | ||
self.weeds.append(newLocation) | ||
self.largeRegions.pop(plantIndex) | ||
print("Locations of weeds:", self.weeds) | ||
|
||
def draw_plant(self): | ||
'''Outlines the contour of the plant.''' | ||
if ImageHelp.equalArray(self.finalImg, None): | ||
self.finalImg = self.editor.outline(self.img, [self.plant], | ||
needToCopy=True) | ||
else: | ||
self.editor.outline(self.finalImg, [self.plant]) | ||
|
||
def draw_weeds(self): | ||
'''Outlines the contour of each weed.''' | ||
if ImageHelp.equalArray(self.finalImg, None): | ||
self.finalImg = self.editor.outline(self.img, self.largeRegions, | ||
needToCopy=True) | ||
else: | ||
self.editor.outline(self.finalImg, self.largeRegions) | ||
|
||
def display_drawings(self): | ||
'''Displays a list of images.''' | ||
imgNames = ['self.img', 'self.imgEdit', 'self.finalImg'] | ||
imgs = [self.img, self.imgEdit, self.finalImg] | ||
ImageHelp.display(imgNames, imgs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import cv2 | ||
import numpy as np | ||
import math | ||
import copy | ||
|
||
class Editor(): | ||
|
||
def __init__(self, img): | ||
self.img = img | ||
(self.height, self.width) = img.shape | ||
self.black = 0 | ||
self.white = 1 | ||
self.blackImg = np.array( [[self.black] * self.width for _ in range(self.height)], dtype='uint8' ) | ||
print('Height: ', self.height, ', Width: ', self.width) | ||
|
||
def find_max(self): | ||
'''Finds the pixel of maximum brightness. This value is usually 255.''' | ||
maxPixel = 0 | ||
for y in range(self.height): | ||
for x in range(self.width): | ||
if self.img[y, x] > maxPixel: | ||
maxPixel = self.img[y, x] | ||
return maxPixel | ||
|
||
def max_locations(self, maxPixel, thresholdBrightness=.6): | ||
'''Finds all locations above a threshold brightness compared to the maximum | ||
brightness. The default is .6 (out of 1.0). No need to calculate the exact | ||
value of brightness.''' | ||
lowestIntensity = thresholdBrightness * maxPixel | ||
ret, thresh = cv2.threshold(self.img, lowestIntensity, 255, cv2.THRESH_BINARY) | ||
locations = copy.deepcopy(self.blackImg) | ||
for y in range(self.height): | ||
for x in range(self.width): | ||
if thresh[y, x] == 255: | ||
locations[y, x] = self.white | ||
return locations | ||
|
||
def find_next_location(self, locations): | ||
'''Goes through each ROW from LEFT TO RIGHT until it encounters the first | ||
true location. Skips every other column and row.''' | ||
for y in range(0, self.height, 2): | ||
for x in range(0, self.width, 2): | ||
if locations[y, x]: | ||
return (y, x) | ||
return None | ||
|
||
def find_centroid(self, region): | ||
contour = cv2.findContours(region, 1, 2)[1][0] | ||
M = cv2.moments(contour) | ||
if M != None: | ||
cX = int(M["m10"] / M["m00"]) | ||
cY = int(M["m01"] / M["m00"]) | ||
return (cY, cX) | ||
else: | ||
return None | ||
|
||
def get_connected_region(self, location, locations): | ||
'''Obtains a single. The format of the region is a numpy array that has the | ||
height and width of the original image. Be careful as this is a call by | ||
reference that directly edits the array of locations.''' | ||
checkLocations = set([location]) | ||
newCheckLocations = set() | ||
checkedLocations = set() | ||
region = copy.deepcopy(self.blackImg) | ||
locations[location[0], location[1]] = self.black # first spot is already checked | ||
while len(checkLocations) > 0: | ||
for check in checkLocations: | ||
if check not in checkedLocations: | ||
checkedLocations.add(check) | ||
if check[0] != 0 and locations[check[0]-1, check[1]] == 1: | ||
newCheckLocations.add((check[0]-1, check[1])) | ||
locations[check[0]-1, check[1]] = self.black | ||
if check[0]+1 != self.height and locations[check[0]+1, check[1]] == 1: | ||
newCheckLocations.add((check[0]+1, check[1])) | ||
locations[check[0]+1, check[1]] = self.black | ||
if check[1] != 0 and locations[check[0], check[1]-1] == 1: | ||
newCheckLocations.add((check[0], check[1]-1)) | ||
locations[check[0], check[1]-1] = self.black | ||
if check[1]+1 != self.width and locations[check[0], check[1]+1] == 1: | ||
newCheckLocations.add((check[0], check[1]+1)) | ||
locations[check[0], check[1]+1] = self.black | ||
for check2 in checkLocations: | ||
region[check2[0], check2[1]] = self.white | ||
checkLocations = newCheckLocations | ||
newCheckLocations = set() | ||
return region | ||
|
||
def get_all_regions(self, locations): | ||
'''Obtains each region. Returns a list of regions.''' | ||
regions = [] | ||
start = self.find_next_location(locations) | ||
while start != None: | ||
region = self.get_connected_region(start, locations) | ||
regions.append(region) | ||
start = self.find_next_location(locations) | ||
return regions | ||
|
||
def keep_large_regions(self, regions, thresholdSize=100): | ||
'''Take the regions and filters those that are below threshold size. Default | ||
threshold size is 100 pixels. This function uses copies.''' | ||
largeRegions = copy.deepcopy(regions) | ||
for i in range(len(regions)-1, -1, -1): | ||
contour = cv2.findContours(regions[i], 1, 2)[1][0] | ||
if cv2.contourArea(contour) < thresholdSize: | ||
largeRegions.pop(i) | ||
return largeRegions | ||
|
||
def obtain_largest_region(self, regions): | ||
'''Take the largest region with greatest circularity (best leaf shape). | ||
May result in a tie.''' | ||
largestRegion = [] | ||
startSize = self.img.size | ||
while len(largestRegion) == 0 and startSize > 0: | ||
startSize //= 2 | ||
largestRegion = self.keep_large_regions(regions, startSize) | ||
while len(largestRegion) != 0: | ||
startSize += 100 | ||
largestRegion = self.keep_large_regions(regions, startSize) | ||
while len(largestRegion) == 0: | ||
startSize -= 1 | ||
largestRegion = self.keep_large_regions(regions, startSize) | ||
return (largestRegion, startSize) | ||
|
||
def mk_points_list(self, regions): | ||
'''This creates an unedited list of points on a polygon.''' | ||
pointsList = [None] * len(regions) | ||
counter = 0 | ||
for region in regions: | ||
pointsList[counter] = self.find_centroid(region) | ||
counter += 1 | ||
return pointsList | ||
|
||
def combine_regions(self, regions): | ||
newImage = copy.deepcopy(self.blackImg) | ||
for y in range(self.height): | ||
for x in range(self.width): | ||
boolArray = [None] * len(regions) | ||
for index in range(len(regions)): | ||
boolArray[index] = regions[index][y, x] | ||
if self.white in boolArray: | ||
newImage[y, x] = self.white | ||
return newImage | ||
|
||
def blacken_regions(self, regions): | ||
'''This is just a check that the regions are correct. Be careful as this | ||
is a call by reference that directly edits the image.''' | ||
for region in regions: | ||
for y in range(self.height): | ||
for x in range(self.width): | ||
if region[y, x]: | ||
self.img[y, x] = self.black | ||
|
||
def outline(self, img, largeRegions, needToCopy=False): | ||
if needToCopy: | ||
img = copy.deepcopy(img) | ||
|
||
radius = 10 | ||
for index in range(len(largeRegions)): | ||
contour = cv2.findContours(largeRegions[index], 1, 2)[1][0] | ||
cv2.drawContours(img, [contour], -1, (255, 0, 255), 2) | ||
|
||
if needToCopy: | ||
return img | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,12 @@ | ||
import cv2 | ||
import numpy as np | ||
import time | ||
|
||
def equalArray(array1, array2): | ||
'''Use if guaranteed non-empty deep arrays.''' | ||
if len(array1) != len(array2): | ||
return False | ||
elif isinstance(array1[0], int) or isinstance(array1[0], np.uint8): | ||
for index in range(len(array1)): | ||
if array1[index] != array2[index]: | ||
return False | ||
return True | ||
else: | ||
for index in range(len(array1)): | ||
if not equalArray(array1[index], array2[index]): | ||
return False | ||
return True | ||
|
||
def grab(cameraNumber): | ||
cap = cv2.VideoCapture(cameraNumber) | ||
|
||
# Define the codec and create VideoWriter object | ||
fourcc = cv2.VideoWriter_fourcc(*'DIVX') | ||
# name, fourcc code, frame rate, frame size, isColor tag | ||
output = cv2.VideoWriter('output.avi', fourcc, 10.0, (640,480), isColor=False) | ||
|
||
frame = cap.read()[1] | ||
cv2.imwrite('Capture\\' + str(int(time.time())) + '.jpg', frame) | ||
|
||
# When everything done, release the capture | ||
cap.release() | ||
output.release() | ||
cv2.destroyAllWindows() | ||
return frame |
Oops, something went wrong.