38 Commits

Author SHA1 Message Date
42ca50e4be Use the largest item instead of quitting when there is more than one 2020-06-08 17:51:27 -05:00
052ea30f73 Move imports to after connection, add service files to pi 2020-05-18 15:49:04 -05:00
dbde062909 add input code 2020-04-27 19:26:38 -05:00
26119e4056 tweak lighting, fix error message 2020-04-03 15:12:41 -05:00
eb479df122 small tweaks 2020-03-18 17:01:11 -05:00
4eac873412 Add consistent error and info messages, tweak image blurring. Remove penny requirement. 2020-03-18 13:52:51 -05:00
4ca4d0c3cd Make sure we can't take the log10 of 0 2020-03-09 10:13:32 -05:00
fa15964b98 Fix indentation, compile 2020-03-06 10:48:21 -06:00
68c1f4dc1a Update magic numbers to be more consistent 2020-03-06 10:38:12 -06:00
0cfa5cb1a8 Add "magic sorting" feature 2020-03-06 09:18:37 -06:00
830a8e7727 Make it actually sort stuff and everything! 2020-03-05 09:24:48 -06:00
4f59cc07d5 Delete 'cv2' 2020-03-03 01:13:17 +00:00
fc4e85a76d Delete 'sort' 2020-03-03 01:13:07 +00:00
8a1b5c92ff Delete 'timeit' 2020-03-03 01:12:59 +00:00
7217cb8b43 lower brightness 2020-03-03 01:12:42 +00:00
52ef77e389 Use grbl's recommended command streaming setup 2020-03-03 01:11:00 +00:00
be2357b9b0 change cropping mechanism 2020-02-25 17:19:08 +00:00
d7a2e2602b Update 'run_detect.py' 2020-02-25 17:17:27 +00:00
dad1ac9e7a Update IP address 2020-02-23 22:28:36 +00:00
4cd3bfc514 fix error in run_detect 2020-02-21 13:25:41 -06:00
cbda6a6cab remove unneeded script 2020-02-21 13:19:05 -06:00
6524ba6cfe add netowrking between pi and laptop, basically add all the components together 2020-02-21 13:17:50 -06:00
47eb71057f add bin position script, update neopixel code 2020-02-21 10:48:24 -06:00
190dc73036 Fix syntax error 2020-02-12 08:48:59 -06:00
5ec6b7a36a Add neopixel control script 2020-02-12 08:37:35 -06:00
0feabe96e5 Update detection to be more reliable 2019-12-18 13:43:39 -06:00
bcfd61a7cf add sorting code, saving to file 2019-12-13 12:28:38 -06:00
e21628b608 output item list and scan 2019-12-13 10:57:02 -06:00
129ad2a762 fix compiled version, improve contrast 2019-12-13 10:13:50 -06:00
af0410be8e compile to machine code again 2019-12-13 10:01:24 -06:00
574bf7050c video capture and detection working now 2019-12-13 09:30:11 -06:00
c40d84705f stuff 2019-12-13 09:21:41 -06:00
72d0015d8f imx135 support 2019-12-13 08:30:14 -06:00
bfa501b6db video stream success with pi camera 2019-12-12 20:15:28 -06:00
32ae8d4559 add arm build of detect library 2019-12-12 17:01:12 -06:00
b1f7129b90 attempt to load video stream from pi directly (not working yet) 2019-12-12 11:28:43 -06:00
a8bc28443f compile to code, runner script 2019-12-12 09:42:46 -06:00
140465d858 start conversion to support video frames and return a UUID 2019-12-12 07:54:33 -06:00
179 changed files with 15977 additions and 353 deletions

BIN
GPUCache/data_0 Normal file

Binary file not shown.

BIN
GPUCache/data_1 Normal file

Binary file not shown.

BIN
GPUCache/data_2 Normal file

Binary file not shown.

BIN
GPUCache/data_3 Normal file

Binary file not shown.

BIN
GPUCache/index Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

BIN
bins.txt Normal file

Binary file not shown.

BIN
bins_default.txt Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
camera.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=UDP Camera Stream
After=network.target auditd.service media-writable.mount
Requires=media-writable.mount
[Service]
ExecStart=/media/writable/run.sh
Restart=always
Type=idle
User=pi
[Install]
WantedBy=multi-user.target

15
client.service Normal file
View File

@ -0,0 +1,15 @@
[Unit]
Description=Python Sorting Client
After=network.target auditd.service media-writable.mount
Requires=media-writable.mount
[Service]
ExecStart=/media/writable/item-sort/pi_client.py
Restart=always
Type=idle
User=root
StartLimitIntervalSec=0
RestartSec=5
WorkingDirectory=/media/writable/item-sort
[Install]
WantedBy=multi-user.target

13
compile.py Normal file
View File

@ -0,0 +1,13 @@
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [
Extension("detect", ["detect.py"]),
#Extension("mymodule2", ["mymodule2.py"]),
# ... all your modules that need be compiled ...
]
setup(
name = 'Item Sorter',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)

101
control_motor.py Normal file
View File

@ -0,0 +1,101 @@
import math
import serial
import RPi.GPIO as gpio
import time
ser = serial.Serial('/dev/ttyUSB0', 115200)
gpio.setmode(gpio.BCM)
gpio.setup(13, gpio.OUT)
pwm = gpio.PWM(13, 100)
pwm.start(13)
verbose = True
print("[ INFO ] Initializing Grbl...")
ser.write(b'\r\n\r\n')
time.sleep(2)
ser.write(b'$RST=#\n')
time.sleep(1)
ser.flushInput()
time.sleep(0.25)
ser.write(b'$X\n')
print("[ INFO ] Grbl is ready.")
def goToBin(bin):
print("[ INFO ] Delivering item to bin: " + str(bin))
adjustedBin = math.floor(bin / 2)
if adjustedBin > 11:
print("[ INFO ] All bins full! Using overflow bin.")
bin = 0;
adjustedBin = 0;
distance = adjustedBin * 18
delay = 0.5 + 0.93 * adjustedBin
command = '$J=X-'
command += str(distance)
command += ' F2000'
print("[ INFO ] Sending command to Grbl: " + command)
command += '\n'
time.sleep(0.25)
ser.write(command.encode('utf-8'))
# s.write("$C\n")
while True:
grbl_out = str(ser.readline().strip()) # Wait for grbl response with carriage return
print(grbl_out)
if int(grbl_out.find('error')) >= 0 :
print("[ EXIT ] Grbl reported an error.")
quit()
elif int(grbl_out.find('ok')) >= 0 :
if verbose: print('[ INFO ] Grbl message: ',grbl_out)
break
print("[ INFO ] Waiting for " + str(delay) + " seconds.")
time.sleep(delay)
if bin % 2 == 0: # tilt to left
print("[ INFO ] Titling motor to left side.")
pwm.ChangeDutyCycle(5)
time.sleep(1)
pwm.ChangeDutyCycle(14)
else:
print("[ INFO ] Titling motor to right side.")
pwm.ChangeDutyCycle(25)
time.sleep(1)
pwm.ChangeDutyCycle(14)
time.sleep(1)
print("[ INFO ] Sending command to Grbl: G0 X0")
ser.write(b'$j=X0 F2000\n')
while True:
grbl_out = str(ser.readline().strip()) # Wait for grbl response with carriage return
if int(grbl_out.find('error')) >= 0 :
print("[ EXIT ] Grbl reported an error.")
quit()
elif int(grbl_out.find('ok')) >= 0 :
if verbose: print('[ INFO ] Grbl message: ',grbl_out)
break
print("[ INFO ] Waiting for " + str(delay) + " seconds.")
time.sleep(delay)
def stopInput():
command = '!'
command += '\n'
ser.write(command.encode('utf-8'))
print(command)
#command2 = str(0x85)
#ser.write(command2.encode('utf-8'))
#while True:
# grbl_out = str(ser.readline().strip()) # Wait for grbl response with carriage return
# if int(grbl_out.find('error')) >= 0 :
# print("[ EXIT ] Grbl reported an error.")
# quit()
# elif int(grbl_out.find('ok')) >= 0 :
# if verbose: print('[ INFO ] Grbl message: ',grbl_out)
# break
def startInput():
ser.write(b'$J=G91 Y-5000 F400\n')
print("intake")
#x = 0
#while True and x < 10:
#x += 1
#grbl_out = str(ser.readline().strip()) # Wait for grbl response with carriage return
#if int(grbl_out.find('error')) >= 0 :
# print("[ EXIT ] Grbl reported an error.")
# quit()
#elif int(grbl_out.find('ok')) >= 0 :
# if verbose: print('[ INFO ] Grbl message: ',grbl_out)
# break

7
control_pixel.py Normal file
View File

@ -0,0 +1,7 @@
import board
import neopixel
pixels = neopixel.NeoPixel(board.D18, 23)
def ledOff():
pixels.fill((0,0,0))
def ledOn():
pixels.fill((2,2,2))

15228
detect.c Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

387
detect.py Normal file
View File

@ -0,0 +1,387 @@
# import the necessary packages
#from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
import math
import time
itemw = 0
itemh = 0
def midpoint(ptA, ptB):
return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)
def sizeVexScrew(iteml):
# Screw Sizing code
# subtract screw head size to find thread length
shead = 0.1
iteml -= shead
#print("Thread Length: " + str(iteml))
iteml *= 8
iteml = round(iteml)
iteml /= 8
return iteml
def sizeStandoff(iteml):
# Standoff Sizing code
iteml *= 2
iteml = round(iteml)
iteml /= 2
return iteml
def larger(a, b):
if a >= b:
return a
else:
return b
def smaller(a, b):
if a < b:
return a
else:
return b
def near(a, b, close):
if abs(a-b) < close:
return True
return False
def swap(a, b):
tmp = a
a = b
b = tmp
"""
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to the input image")
#ap.add_argument("-c", "--cascade", required=True,
# help="path to the cascade")
ap.add_argument("-w", "--width", type=float, required=True,
help="width of the left-most object in the image (in inches)")
ap.add_argument("-n", "--number", type=int, required=False,
help="object # to measure (from left to right)")
ap.add_argument("-s", "--show", action="store_true",
help="show on the screen")
args = vars(ap.parse_args())
args2 = ap.parse_args()"""
def detect(calibration_width, img_file, show, quick):
list = []
#if type(args["number"]) == type(selected):
# selected = args["number"]
# load the image, convert it to grayscale, and blur it slightly
image = None
#print(str(type(img_file)))
if str(type(img_file)) == "<class 'numpy.ndarray'>":
image = img_file.copy()
else:
image = cv2.imread(img_file)
#image = img_file.copy()
image = cv2.resize(image, (math.floor(image.shape[1]*0.5), math.floor(image.shape[0]*0.5)))
#image = cv2.resize(image, (1000, int(image.shape[0]/image.shape[1] * 1000)), interpolation=cv2.INTER_NEAREST)
if show and not quick:
cv2.namedWindow("Item Sorter")
cv2.imshow("Item Sorter", image)
cv2.waitKey(0)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
# perform edge detection, then perform a dilation + erosion to
# close gaps in between object edges
edged = cv2.Canny(gray, 50, 100)
edged = cv2.dilate(edged, None, iterations=2)
edged = cv2.erode(edged, None, iterations=2)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)
edged = cv2.dilate(edged, None, iterations=2)
#edged = cv2.erode(edged, None, iterations=1)
#edged = cv2.dilate(edged, None, iterations=1)
if show and not quick:
cv2.imshow("Item Sorter", edged)
cv2.waitKey(0)
# find contours in the edge map
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# sort the contours from left-to-right and initialize the
# 'pixels per metric' calibration variable
#(cnts, _) = contours.sort_contours(cnts)
pixelsPerMetric = None
num = 0
# Calibration loop
"""
for c in cnts:
# if the contour is not sufficiently large, ignore it
if cv2.contourArea(c) < 100:
continue
# compute the rotated bounding box of the contour
orig = image.copy()
box = cv2.minAreaRect(c)
# xpos,ypos,w,h = cv2.boundingRect(c)
# crop_img = orig[ypos:ypos+h, xpos:xpos+w]
# cv2.imwrite("object_images/IMG_" + str(w*h) + ".jpg", crop_img) # create training images
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype="int")
#box = perspective.order_points(box)
(tl, tr, br, bl) = box
(tltrX, tltrY) = midpoint(tl, tr)
(blbrX, blbrY) = midpoint(bl, br)
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
dA = np.linalg.norm(np.array((tltrX, tltrY, 0)) -
np.array((blbrX, blbrY, 0)))
dB = np.linalg.norm(np.array((tlblX, tlblY, 0)) -
np.array((trbrX, trbrY, 0)))
area_box = dA * dB
(x, y), radius = cv2.minEnclosingCircle(c)
area_contour = cv2.contourArea(c)
area_circle = math.pi * pow(radius, 2)
boxiness = area_contour / area_box
circleness = area_contour / area_circle
circular = False
rectangular = False
if boxiness > circleness:
rectangular = True
cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)
else:
circular = True
cv2.circle(orig, (int(x), int(y)), int(radius), (0, 255, 0), 3)
mask = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask, [c], 0, 255, -1)
#pixelpoints = np.transpose(np.nonzero(mask))
hsv = cv2.cvtColor(orig, cv2.COLOR_BGR2HSV)
mean_val = cv2.mean(hsv, mask=mask)
#print(str(mean_val[0]))
#print(", " + str(mean_val[0]/mean_val[2]))
#print(", " + str(mean_val[2]/mean_val[1]))
if pixelsPerMetric is None and circular is True and near(mean_val[0], 16, 4.5):
# and near(mean_val[0], 63, 40) is True and near(mean_val[1], 108, 40) is True and near(mean_val[2], 104, 40) is True:
pixelsPerMetric = smaller(dA, dB) / calibration_width
continue
"""
pixelsPerMetric = 25
orig = image.copy()
objtype = "Object"
objname = ""
c = None
# loop over the contours individually
if len(cnts) == 0:
return ((),edged)
area = cv2.contourArea(cnts[0])
if area < 400:
area = 0
for contour in cnts:
if cv2.contourArea(contour) >= area and cv2.contourArea(contour) > 400:
area = cv2.contourArea(contour)
c = contour
if c is not None:
#orig = image.copy()
num += 1
# if the contour is not sufficiently large, ignore it
#pixelsPerMetric = 75
#if cv2.contourArea(c) < 300 or pixelsPerMetric is None:
#continue
# compute the rotated bounding box of the contour
box = cv2.minAreaRect(c)
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype="int")
# unpack the ordered bounding box, then compute the midpoint
# between the top-left and top-right coordinates, followed by
# the midpoint between bottom-left and bottom-right coordinates
(tl, tr, br, bl) = box
(tltrX, tltrY) = midpoint(tl, tr)
(blbrX, blbrY) = midpoint(bl, br)
# compute the midpoint between the top-left and top-right points,
# followed by the midpoint between the top-right and bottom-right
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
# compute the Euclidean distance between the midpoints
dA = np.linalg.norm(np.array((tltrX, tltrY, 0)) -
np.array((blbrX, blbrY, 0)))
dB = np.linalg.norm(np.array((tlblX, tlblY, 0)) -
np.array((trbrX, trbrY, 0)))
dimA = dA / pixelsPerMetric
dimB = dB / pixelsPerMetric
# Item detection
area_box = dA * dB
(x, y), radius = cv2.minEnclosingCircle(c)
area_contour = cv2.contourArea(c)
area_circle = math.pi * pow(radius, 2)
boxiness = area_contour / area_box
circleness = area_contour / area_circle
circular = False
rectangular = False
if boxiness > circleness:
rectangular = True
#cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)
else:
circular = True
cv2.circle(orig, (int(x), int(y)), int(radius), (255, 0, 0), 2)
objtype = "Object"
itemw = larger(dimA, dimB)
itemwr = itemw
itemwr *= 8
itemwr = round(itemwr)
itemwr /= 8
itemh = smaller(dimA, dimB)
itemhr = itemh
itemhr *= 16
itemhr = round(itemhr)
itemhr /= 16
if circular and itemwr == 0.75:
objtype = "Penny"
iteml = 0
"""else:
if circular and near(radius * 2 / pixelsPerMetric, 0.4, 0.03):
# Keps nut or spacer
objtype = "Spacer"
mask = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask, [c], 0, 255, -1)
#pixelpoints = np.transpose(np.nonzero(mask))
hsv = cv2.cvtColor(orig, cv2.COLOR_BGR2HSV)
mean_val = cv2.mean(hsv, mask=mask)
mean_rgb = cv2.mean(orig, mask=mask)
if near(mean_rgb[2], 59, 3) and near(mean_val[1], 85, 5): #and near(mean_val[2], 78, 5):
objtype = "Keps Nut"
print(str(mean_rgb[2]) + objtype + str(mean_val[1]))
elif circular and near(radius / pixelsPerMetric, 0.23, 0.02):
objtype = "Washer"
#print(str(radius * 2 / pixelsPerMetric) + objtype)
epsilon = 3 # 0.02*cv2.arcLength(c,True)
# print(str(epsilon))
approx = cv2.approxPolyDP(c, epsilon, True)
hull = cv2.convexHull(approx, returnPoints=False)
hull2 = cv2.convexHull(c)
defects = cv2.convexityDefects(c, hull)
#print(str(defects.size) + " match")
cv2.drawContours(orig, (hull2), -1, (0, 0, 255), 3)
cv2.drawContours(orig, (approx), -1, (255, 0, 0), 3)
convexness = area_contour / cv2.contourArea(hull2)
#print(str(convexness) + " % fill")
# if not cv2.isContourConvex(approx):
# if cv2.matchShapes(hull, c, 1, 0.0) > 1:
if defects is not None and defects.size > 5 and (convexness < 0.9 or boxiness < 0.75) and rectangular:
objtype = "Screw"
iteml = larger(dimA, dimB)
#print("Screw Length (RAW): " + str(iteml))
iteml = sizeVexScrew(radius * 2 / pixelsPerMetric)
#print("Rounded Length: " + str(iteml))
else:
if itemhr == 0.3125 and rectangular:
objtype = "Standoff"
iteml = sizeStandoff(itemw)
if itemhr == 0.1875 and rectangular:
objtype = "Axle"
iteml = (radius * 2 / pixelsPerMetric + itemw) / 2
"""
rows, cols = orig.shape[:2]
[vx, vy, xx, yy] = cv2.fitLine(c, cv2.DIST_L2, 0, 0.01, 0.01)
lefty = int((-xx*vy/vx) + yy)
righty = int(((cols-xx)*vy/vx)+yy)
# cv2.line(orig,(cols-1,righty),(0,lefty),(0,255,0),2)
slope = (lefty - righty) / (1 - cols)
angle = math.atan(slope)
xpos = x - math.cos(angle) * radius
ypos = y - math.sin(angle) * radius
xpos2 = x + math.cos(angle) * radius
ypos2 = y + math.sin(angle) * radius
if xpos > xpos2:
swap(xpos, xpos2)
swap(ypos, ypos2)
if rectangular:
cv2.line(orig, (int(xpos), int(ypos)),
(int(xpos2), int(ypos2)), (255, 127, 0), 2)
# print(str(iteml))
# draw the object sizes on the image
# cv2.putText(orig, "{:.5f}in".format(itemhr),
# (int(trbrX + 20), int(trbrY)), cv2.FONT_HERSHEY_SIMPLEX,
# 0.65, (255, 255, 255), 2)
if objtype != "Penny":
objtype = magicSort(c)
if objtype == "Object":
objtype = magicSort(c)
output = "{:.2f}in".format(itemw) + " x {:.2f}in".format(itemh)
if circular:
cv2.putText(orig, str(objtype),
(int(x - 25), int(y + radius + 20)
), cv2.FONT_HERSHEY_SIMPLEX,
0.6, (50, 50, 220), 2)
else:
cv2.putText(orig, str(objtype),
(int(xpos2 + 10), int(ypos2 + 20)
), cv2.FONT_HERSHEY_SIMPLEX,
0.6, (50, 50, 220), 2)
output = ""
objname = objtype;
"""
if objtype == "Screw" or objtype == "Standoff":
output = str(iteml) + "in"
objname += str(iteml)
if objtype == "Axle":
output = "{:.2f}in".format(iteml)
objname += str(itemwr)
#print(objname)
"""
list.append(objname)
if circular:
cv2.putText(orig, output, # print data
(int(x - 25), int(y + radius + 40)
), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (50, 50, 220), 1)
else:
cv2.putText(orig, output, # print data
(int(xpos2 + 10), int(ypos2 + 40)
), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (50, 50, 220), 1)
# show the output image
if show and not quick:
cv2.imshow("Item Sorter", orig)
#cv2.waitKey(1)
if not quick:
cv2.waitKey(0)
return (list, edged)
def magicSort(contour):
moments = cv2.moments(contour)
humoments = cv2.HuMoments(moments)
#humoments[6] = abs(humoments[6]) #it's possible for the last number to change sign if item is mirrored
#magicNumber1 = 0
#magicNumber2 = 0
name = "Object"
for i in range(0,7):
if humoments[i] == 0:
humoments[i] = 0.1;
humoments[i] = -1 * math.copysign(1.0, humoments[i]) * math.log10(abs(humoments[i]))
if i > 1:
humoments[i] = int(round(humoments[i][0] / 8) * 8)
if i != 4 and i != 6:
name += ", " + str(abs(int(humoments[i][0])))
#magicNumber1 += abs(humoments[i][0])
else:
humoments[i] = int(round(humoments[i][0] * 4) * 16)
name += ", " + str(abs(int(humoments[i][0])))
#magicNumber2 += abs(humoments[i][0])
#magicNumber += humoments[i][0]
#print(str(humoments))
#print(magicNumber)
#name = "Unknown: " + str(int(magicNumber1)) + ", " + str(int(magicNumber2))
#print(name)
return name

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

BIN
img.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

355
main.py
View File

@ -1,353 +1,2 @@
# import the necessary packages from logic import main # this comes from a compiled binary
#from imutils import perspective main ()
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
import math
import time
itemw = 0
itemh = 0
def midpoint(ptA, ptB):
return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)
def sizeVexScrew(iteml):
# Screw Sizing code
# subtract screw head size to find thread length
shead = 0.1
iteml -= shead
#print("Thread Length: " + str(iteml))
iteml *= 8
iteml = round(iteml)
iteml /= 8
return iteml
def sizeStandoff(iteml):
# Standoff Sizing code
iteml *= 2
iteml = round(iteml)
iteml /= 2
return iteml
def larger(a, b):
if a >= b:
return a
else:
return b
def smaller(a, b):
if a < b:
return a
else:
return b
def near(a, b, close):
if abs(a-b) < close:
return True
return False
def swap(a, b):
tmp = a
a = b
b = tmp
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to the input image")
#ap.add_argument("-c", "--cascade", required=True,
# help="path to the cascade")
ap.add_argument("-w", "--width", type=float, required=True,
help="width of the left-most object in the image (in inches)")
ap.add_argument("-n", "--number", type=int, required=False,
help="object # to measure (from left to right)")
ap.add_argument("-s", "--show", action="store_true",
help="show on the screen")
args = vars(ap.parse_args())
args2 = ap.parse_args()
selected = 2
if type(args["number"]) == type(selected):
selected = args["number"]
# load the image, convert it to grayscale, and blur it slightly
image = cv2.imread(args["image"])
#image = cv2.resize(image, (int(image.shape[1]*1), int(image.shape[0]*1)))
image = cv2.resize(image, (1000, int(
image.shape[0]/image.shape[1] * 1000)), interpolation=cv2.INTER_NEAREST)
if args2.show:
cv2.namedWindow("Item Sorter")
cv2.imshow("Item Sorter", image)
cv2.waitKey(0)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
if args2.show:
cv2.imshow("Item Sorter", gray)
cv2.waitKey(0)
# perform edge detection, then perform a dilation + erosion to
# close gaps in between object edges
edged = cv2.Canny(gray, 50, 100)
if args2.show:
cv2.imshow("Item Sorter", edged)
cv2.waitKey(0)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)
if args2.show:
cv2.imshow("Item Sorter", edged)
cv2.waitKey(0)
# find contours in the edge map
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# sort the contours from left-to-right and initialize the
# 'pixels per metric' calibration variable
#(cnts, _) = contours.sort_contours(cnts)
pixelsPerMetric = None
num = 0
# Calibration loop
for c in cnts:
# if the contour is not sufficiently large, ignore it
if cv2.contourArea(c) < 100:
continue
# compute the rotated bounding box of the contour
orig = image.copy()
box = cv2.minAreaRect(c)
# xpos,ypos,w,h = cv2.boundingRect(c)
# crop_img = orig[ypos:ypos+h, xpos:xpos+w]
# cv2.imwrite("object_images/IMG_" + str(w*h) + ".jpg", crop_img) # create training images
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype="int")
#box = perspective.order_points(box)
(tl, tr, br, bl) = box
(tltrX, tltrY) = midpoint(tl, tr)
(blbrX, blbrY) = midpoint(bl, br)
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
dA = np.linalg.norm(np.array((tltrX, tltrY, 0)) -
np.array((blbrX, blbrY, 0)))
dB = np.linalg.norm(np.array((tlblX, tlblY, 0)) -
np.array((trbrX, trbrY, 0)))
area_box = dA * dB
(x, y), radius = cv2.minEnclosingCircle(c)
area_contour = cv2.contourArea(c)
area_circle = math.pi * pow(radius, 2)
boxiness = area_contour / area_box
circleness = area_contour / area_circle
circular = False
rectangular = False
if boxiness > circleness:
rectangular = True
cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)
else:
circular = True
cv2.circle(orig, (int(x), int(y)), int(radius), (0, 255, 0), 2)
mask = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask, [c], 0, 255, -1)
#pixelpoints = np.transpose(np.nonzero(mask))
hsv = cv2.cvtColor(orig, cv2.COLOR_BGR2HSV)
mean_val = cv2.mean(hsv, mask=mask)
#print(str(mean_val[0]))
#print(", " + str(mean_val[0]/mean_val[2]))
#print(", " + str(mean_val[2]/mean_val[1]))
if pixelsPerMetric is None and circular is True and near(mean_val[0], 16, 4.5):
# and near(mean_val[0], 63, 40) is True and near(mean_val[1], 108, 40) is True and near(mean_val[2], 104, 40) is True:
pixelsPerMetric = smaller(dA, dB) / args["width"]
orig = image.copy()
# loop over the contours individually
for c in cnts:
#orig = image.copy()
num += 1
# if the contour is not sufficiently large, ignore it
if cv2.contourArea(c) < 100 or pixelsPerMetric is None:
continue
# compute the rotated bounding box of the contour
box = cv2.minAreaRect(c)
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype="int")
# order the points in the contour such that they appear
# in top-left, top-right, bottom-right, and bottom-left
# order, then draw the outline of the rotated bounding
# box
#box = perspective.order_points(box)
# loop over the original points and draw them
# for (x, y) in box:
#cv2.circle(orig, (int(x), int(y)), 5, (0, 0, 255), -1)
# unpack the ordered bounding box, then compute the midpoint
# between the top-left and top-right coordinates, followed by
# the midpoint between bottom-left and bottom-right coordinates
(tl, tr, br, bl) = box
(tltrX, tltrY) = midpoint(tl, tr)
(blbrX, blbrY) = midpoint(bl, br)
# compute the midpoint between the top-left and top-right points,
# followed by the midpoint between the top-right and bottom-right
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
# draw the midpoints on the image
#cv2.circle(orig, (int(tltrX), int(tltrY)), 5, (255, 0, 0), -1)
#cv2.circle(orig, (int(blbrX), int(blbrY)), 5, (255, 0, 0), -1)
#cv2.circle(orig, (int(tlblX), int(tlblY)), 5, (255, 0, 0), -1)
#cv2.circle(orig, (int(trbrX), int(trbrY)), 5, (255, 0, 0), -1)
# draw lines between the midpoints
# compute the Euclidean distance between the midpoints
dA = np.linalg.norm(np.array((tltrX, tltrY, 0)) -
np.array((blbrX, blbrY, 0)))
dB = np.linalg.norm(np.array((tlblX, tlblY, 0)) -
np.array((trbrX, trbrY, 0)))
dimA = dA / pixelsPerMetric
dimB = dB / pixelsPerMetric
if num == selected or args2.show:
area_box = dA * dB
(x, y), radius = cv2.minEnclosingCircle(c)
area_contour = cv2.contourArea(c)
area_circle = math.pi * pow(radius, 2)
boxiness = area_contour / area_box
circleness = area_contour / area_circle
circular = False
rectangular = False
if boxiness > circleness:
rectangular = True
#cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)
else:
circular = True
cv2.circle(orig, (int(x), int(y)), int(radius), (0, 255, 0), 1)
objtype = "Unknown"
itemw = larger(dimA, dimB)
itemwr = itemw
itemwr *= 8
itemwr = round(itemwr)
itemwr /= 8
itemh = smaller(dimA, dimB)
itemhr = itemh
itemhr *= 16
itemhr = round(itemhr)
itemhr /= 16
if circular and itemwr == 0.75:
objtype = "Penny"
iteml = 0
else:
if circular and near(radius * 2 / pixelsPerMetric, 0.38, 0.03):
# Keps nut or spacer
objtype = "Spacer"
mask = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask, [c], 0, 255, -1)
#pixelpoints = np.transpose(np.nonzero(mask))
hsv = cv2.cvtColor(orig, cv2.COLOR_BGR2HSV)
mean_val = cv2.mean(hsv, mask=mask)
#print(str(mean_val[0]))
if near(mean_val[0], 47, 5) and near(mean_val[1], 70, 5) and near(mean_val[2], 78, 5):
objtype = "Keps Nut"
if circular and near(radius / pixelsPerMetric, 0.23, 0.02):
objtype = "Washer"
epsilon = 3 # 0.02*cv2.arcLength(c,True)
# print(str(epsilon))
approx = cv2.approxPolyDP(c, epsilon, True)
hull = cv2.convexHull(approx, returnPoints=False)
hull2 = cv2.convexHull(c)
defects = cv2.convexityDefects(c, hull)
#print(str(defects.size) + " match")
cv2.drawContours(orig, (hull2), -1, (0, 0, 255), 3)
cv2.drawContours(orig, (approx), -1, (255, 0, 0), 3)
convexness = area_contour / cv2.contourArea(hull2)
#print(str(convexness) + " % fill")
# if not cv2.isContourConvex(approx):
# if cv2.matchShapes(hull, c, 1, 0.0) > 1:
if defects is not None and defects.size > 5 and (convexness < 0.9 or boxiness < 0.75) and rectangular:
objtype = "Screw"
iteml = larger(dimA, dimB)
#print("Screw Length (RAW): " + str(iteml))
iteml = sizeVexScrew(radius * 2 / pixelsPerMetric)
#print("Rounded Length: " + str(iteml))
else:
if itemhr == 0.3125 and rectangular:
objtype = "Standoff"
iteml = sizeStandoff(itemw)
if itemhr == 0.1875 and rectangular:
objtype = "Axle"
iteml = (radius * 2 / pixelsPerMetric + itemw) / 2
rows, cols = orig.shape[:2]
[vx, vy, xx, yy] = cv2.fitLine(c, cv2.DIST_L2, 0, 0.01, 0.01)
lefty = int((-xx*vy/vx) + yy)
righty = int(((cols-xx)*vy/vx)+yy)
# cv2.line(orig,(cols-1,righty),(0,lefty),(0,255,0),2)
slope = (lefty - righty) / (1 - cols)
angle = math.atan(slope)
xpos = x - math.cos(angle) * radius
ypos = y - math.sin(angle) * radius
xpos2 = x + math.cos(angle) * radius
ypos2 = y + math.sin(angle) * radius
if xpos > xpos2:
swap(xpos, xpos2)
swap(ypos, ypos2)
if rectangular:
cv2.line(orig, (int(xpos), int(ypos)),
(int(xpos2), int(ypos2)), (0, 255, 0), 1)
# print(str(iteml))
# draw the object sizes on the image
if args2.show:
# cv2.putText(orig, "{:.5f}in".format(itemhr),
# (int(trbrX + 20), int(trbrY)), cv2.FONT_HERSHEY_SIMPLEX,
# 0.65, (255, 255, 255), 2)
if circular:
cv2.putText(orig, str(objtype),
(int(x - 25), int(y + radius + 20)
), cv2.FONT_HERSHEY_SIMPLEX,
0.55, (255, 255, 255), 2)
else:
cv2.putText(orig, str(objtype),
(int(xpos2 + 10), int(ypos2 + 20)
), cv2.FONT_HERSHEY_SIMPLEX,
0.55, (255, 255, 255), 2)
output = ""
if objtype == "Unknown":
output = "{:.2f}in".format(itemw) + " x {:.2f}in".format(itemh)
if objtype == "Screw" or objtype == "Standoff":
output = str(iteml) + "in"
if objtype == "Axle":
output = "{:.2f}in".format(iteml)
if circular:
cv2.putText(orig, output, # print data
(int(x - 25), int(y + radius + 35)
), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (255, 255, 255), 1)
else:
cv2.putText(orig, output, # print data
(int(xpos2 + 10), int(ypos2 + 35)
), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (255, 255, 255), 1)
# show the output image
cv2.imshow("Item Sorter", orig)
cv2.waitKey(1)
cv2.waitKey(0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Some files were not shown because too many files have changed in this diff Show More