Commit
·
3858d9a
1
Parent(s):
77f6352
Upload 6 files
Browse filesadded scripts for creating the model and loading it onto the raspberry pi
- create-model/computer_requirements.txt +6 -0
- create-model/create_image_classification_model.ipynb +0 -0
- create-model/create_training_data_array.py +40 -0
- create-model/testing-tflite-model-com.py +35 -0
- rpi-object-detection/motion_detection_and_image_classification.py +217 -0
- rpi-object-detection/rpi_requirements.txt +5 -0
create-model/computer_requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tensorflow == 2.9.1
|
| 2 |
+
Pillow == 9.2.0
|
| 3 |
+
numpy == 1.23.2
|
| 4 |
+
opencv-python == 4.6.0.66
|
| 5 |
+
matplotlib == 3.5.3
|
| 6 |
+
scikit-learn == 1.1.2
|
create-model/create_image_classification_model.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
create-model/create_training_data_array.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# script to create training data npy file from the database of images
|
| 2 |
+
# the npy file can then be uploaded to google drive and read in the jupyter notebook
|
| 3 |
+
# can then create training_data for model training
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import cv2
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
# initialize target image size for the training and testing data
|
| 10 |
+
img_height = 128
|
| 11 |
+
img_width = 128
|
| 12 |
+
|
| 13 |
+
categories = ["straight-liftarm", 'pins', 'bent-liftarm', 'gears-and-disc', 'special-connector', 'axles', 'axle-connectors-stoppers']
|
| 14 |
+
|
| 15 |
+
training_data = []
|
| 16 |
+
def get_category_images(list,path,label):
|
| 17 |
+
#print("old:", str(len(training_data)))
|
| 18 |
+
current = len(training_data)
|
| 19 |
+
for i in range(len(list)):
|
| 20 |
+
try:
|
| 21 |
+
image = cv2.imread(os.path.join(path,list[i]),
|
| 22 |
+
cv2.IMREAD_GRAYSCALE)
|
| 23 |
+
image = cv2.resize(image, (128,128))
|
| 24 |
+
training_data.append([image, label])
|
| 25 |
+
except Exception:
|
| 26 |
+
pass
|
| 27 |
+
new = len(training_data)
|
| 28 |
+
print(new - current)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
for cat in categories:
|
| 32 |
+
cat_path = "RPI3_project/lego-test-data/database/" + cat
|
| 33 |
+
cat_list = os.listdir(cat_path)
|
| 34 |
+
cat_label = categories.index(cat)
|
| 35 |
+
get_category_images(cat_list, cat_path, cat_label)
|
| 36 |
+
|
| 37 |
+
print(len(training_data))
|
| 38 |
+
td_array = np.array(training_data)
|
| 39 |
+
len(td_array)
|
| 40 |
+
np.save('td_array_7cat', td_array)
|
create-model/testing-tflite-model-com.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# to test tflite model on individual images
|
| 2 |
+
# run on your own computer as raspberry pi can't install tensorflow, and we need the img_to_array function
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
from tensorflow.keras.preprocessing.image import load_img
|
| 7 |
+
from tensorflow.keras.preprocessing.image import img_to_array
|
| 8 |
+
from PIL import Image, ImageOps
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# Load TFLite model and allocate tensors.
|
| 12 |
+
interpreter = tf.lite.Interpreter(model_path="OGmodel.tflite")
|
| 13 |
+
interpreter.allocate_tensors()
|
| 14 |
+
|
| 15 |
+
# Get input and output tensors.
|
| 16 |
+
input_details = interpreter.get_input_details()
|
| 17 |
+
output_details = interpreter.get_output_details()
|
| 18 |
+
|
| 19 |
+
# Test model on random input data.
|
| 20 |
+
input_shape = input_details[0]['shape']
|
| 21 |
+
input_image = Image.open('lego-testing/testing/12image.jpg')
|
| 22 |
+
input_image = ImageOps.grayscale(input_image)
|
| 23 |
+
input_image = input_image.resize((28,28))
|
| 24 |
+
|
| 25 |
+
input_data = img_to_array(input_image)
|
| 26 |
+
input_data.resize(1,28,28,1)
|
| 27 |
+
#input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
|
| 28 |
+
interpreter.set_tensor(input_details[0]['index'], input_data)
|
| 29 |
+
|
| 30 |
+
interpreter.invoke()
|
| 31 |
+
|
| 32 |
+
# The function `get_tensor()` returns a copy of the tensor data.
|
| 33 |
+
# Use `tensor()` in order to get a pointer to the tensor.
|
| 34 |
+
output_data = interpreter.get_tensor(output_details[0]['index'])
|
| 35 |
+
print(np.argmax(output_data[0]))
|
rpi-object-detection/motion_detection_and_image_classification.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This program combines motion detection and object classification. It will ouput the most probable category of lego pieces
|
| 2 |
+
# after the picamera detects it in realtime.
|
| 3 |
+
# The motion detection portion of the script was adapted from pyimagesearch's project
|
| 4 |
+
# 'Building a Raspberry Pi security camera with OpenCV' and can be found at
|
| 5 |
+
# https://pyimagesearch.com/2019/03/25/building-a-raspberry-pi-security-camera-with-opencv/
|
| 6 |
+
|
| 7 |
+
# To run, open the terminal in RPI and navigate to folder containing the python script.
|
| 8 |
+
# Run python3 'path_to_script' --conf conf.json
|
| 9 |
+
|
| 10 |
+
# This script, when run, will activate the picamera to detect motion of objects (preferably against a white background)
|
| 11 |
+
# and enclose it in a green boundary box.
|
| 12 |
+
# If successive frames of motion is detected by the picamera, the boundary box will be extracted and image saved to a
|
| 13 |
+
# pre-specified folder in the RPI. The image contrast will be increased, and resized before being converted into an input tensor.
|
| 14 |
+
# The input tensor will be passed into the interpretor (a tensorflow lite model) which will output a probability vector.
|
| 15 |
+
# The vector index of the highest probability will be extracted to output the most likely class of the lego piece.
|
| 16 |
+
|
| 17 |
+
# This script can be modified to take the images required for the database. The motionCounter can be decreased to take more images.
|
| 18 |
+
|
| 19 |
+
from picamera.array import PiRGBArray
|
| 20 |
+
from picamera import PiCamera
|
| 21 |
+
import argparse
|
| 22 |
+
import warnings
|
| 23 |
+
import datetime
|
| 24 |
+
import imutils
|
| 25 |
+
import json
|
| 26 |
+
import time
|
| 27 |
+
import cv2
|
| 28 |
+
import os
|
| 29 |
+
|
| 30 |
+
#imports and initialisations for image recognition
|
| 31 |
+
from tflite_runtime.interpreter import Interpreter
|
| 32 |
+
from PIL import Image, ImageOps
|
| 33 |
+
import numpy as np
|
| 34 |
+
|
| 35 |
+
# Load TFLite model and allocate tensors.
|
| 36 |
+
interpreter = Interpreter(model_path="lego_tflite_model/detect.tflite") # insert path to the tflite model
|
| 37 |
+
interpreter.allocate_tensors()
|
| 38 |
+
path = r'/home/nullspacepi/Desktop/opencv-test/lego-pieces' # create variable for path to where camera pictures will be saved to
|
| 39 |
+
|
| 40 |
+
# Get input and output tensors.
|
| 41 |
+
input_details = interpreter.get_input_details()
|
| 42 |
+
output_details = interpreter.get_output_details()
|
| 43 |
+
input_shape = input_details[0]['shape']
|
| 44 |
+
|
| 45 |
+
# define a function that will convert the image captured into an array
|
| 46 |
+
def img_to_array(img, data_format='channels_last', dtype='float32'):
|
| 47 |
+
if data_format not in {'channels_first', 'channels_last'}:
|
| 48 |
+
raise ValueError('Unknown data_format: %s' % data_format)
|
| 49 |
+
|
| 50 |
+
x = np.asarray(img, dtype=dtype)
|
| 51 |
+
if len(x.shape) == 3:
|
| 52 |
+
if data_format == 'channels_first':
|
| 53 |
+
x = x.transpose(2, 0, 1)
|
| 54 |
+
elif len(x.shape) == 2:
|
| 55 |
+
if data_format == 'channels_first':
|
| 56 |
+
x = x.reshape((1, x.shape[0], x.shape[1]))
|
| 57 |
+
else:
|
| 58 |
+
x = x.reshape((x.shape[0], x.shape[1], 1))
|
| 59 |
+
else:
|
| 60 |
+
raise ValueError('Unsupported image shape: %s' % (x.shape,))
|
| 61 |
+
return x
|
| 62 |
+
|
| 63 |
+
# define a function that will increase the contrast of the image by manipulating its array. This will increase the likelihood
|
| 64 |
+
# of its features to be detected by the image classification tensorflow model
|
| 65 |
+
def increase_contrast_more(s):
|
| 66 |
+
minval = np.percentile(s, 2)
|
| 67 |
+
maxval = np.percentile(s, 98)
|
| 68 |
+
npImage = np.clip(s, minval, maxval)
|
| 69 |
+
|
| 70 |
+
npImage = npImage.astype(int)
|
| 71 |
+
|
| 72 |
+
min=np.min(npImage) # result=144
|
| 73 |
+
max=np.max(npImage) # result=216
|
| 74 |
+
|
| 75 |
+
# Make a LUT (Look-Up Table) to translate image values
|
| 76 |
+
LUT=np.zeros(256,dtype=np.float32)
|
| 77 |
+
LUT[min:max+1]=np.linspace(start=0,stop=255,num=(max-min)+1,endpoint=True,dtype=np.float32)
|
| 78 |
+
s_clipped = LUT[npImage]
|
| 79 |
+
return s_clipped
|
| 80 |
+
|
| 81 |
+
# Read the labels from the text file as a Python list.
|
| 82 |
+
def load_labels(path):
|
| 83 |
+
with open(path, 'r') as f:
|
| 84 |
+
return [line.strip() for i, line in enumerate(f.readlines())]
|
| 85 |
+
|
| 86 |
+
# Read class labels and create a vector.
|
| 87 |
+
labels = load_labels("lego_tflite_model/labelmap.txt")
|
| 88 |
+
|
| 89 |
+
# construct the argument parser and parse the arguments
|
| 90 |
+
ap = argparse.ArgumentParser()
|
| 91 |
+
ap.add_argument("-c", "--conf", required=True, help="path to the JSON configuration file")
|
| 92 |
+
args = vars(ap.parse_args())
|
| 93 |
+
|
| 94 |
+
# filter warnings, load the configuration
|
| 95 |
+
warnings.filterwarnings("ignore")
|
| 96 |
+
conf = json.load(open(args["conf"]))
|
| 97 |
+
client = None
|
| 98 |
+
|
| 99 |
+
# initialize the camera and grab a reference to the raw camera capture
|
| 100 |
+
camera = PiCamera()
|
| 101 |
+
camera.resolution = tuple(conf["resolution"])
|
| 102 |
+
camera.framerate = conf["fps"]
|
| 103 |
+
rawCapture = PiRGBArray(camera, size=tuple(conf["resolution"]))
|
| 104 |
+
|
| 105 |
+
# allow the camera to warmup, then initialize the average frame, last
|
| 106 |
+
# uploaded timestamp, and frame motion counter
|
| 107 |
+
print("[INFO] warming up...")
|
| 108 |
+
time.sleep(conf["camera_warmup_time"])
|
| 109 |
+
avg = None
|
| 110 |
+
motionCounter = 0
|
| 111 |
+
image_number = 0
|
| 112 |
+
|
| 113 |
+
# capture frames from the camera
|
| 114 |
+
for f in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
|
| 115 |
+
# grab the raw NumPy array representing the image and initialize
|
| 116 |
+
# the timestamp and occupied/unoccupied text
|
| 117 |
+
frame = f.array
|
| 118 |
+
text = "No piece"
|
| 119 |
+
|
| 120 |
+
# resize the frame, convert it to grayscale, and blur it
|
| 121 |
+
frame = imutils.resize(frame, width=500)
|
| 122 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 123 |
+
gray = cv2.GaussianBlur(gray, (21, 21), 0)
|
| 124 |
+
|
| 125 |
+
# if the average frame is None, initialize it
|
| 126 |
+
if avg is None:
|
| 127 |
+
print("[INFO] starting background model...")
|
| 128 |
+
avg = gray.copy().astype("float")
|
| 129 |
+
rawCapture.truncate(0)
|
| 130 |
+
continue
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
# accumulate the weighted average between the current frame and
|
| 134 |
+
# previous frames, then compute the difference between the current
|
| 135 |
+
# frame and running average
|
| 136 |
+
cv2.accumulateWeighted(gray, avg, 0.5)
|
| 137 |
+
frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))
|
| 138 |
+
|
| 139 |
+
# threshold the delta image, dilate the thresholded image to fill
|
| 140 |
+
# in holes, then find contours on thresholded image
|
| 141 |
+
thresh = cv2.threshold(frameDelta, conf["delta_thresh"], 255,
|
| 142 |
+
cv2.THRESH_BINARY)[1]
|
| 143 |
+
thresh = cv2.dilate(thresh, None, iterations=2)
|
| 144 |
+
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
|
| 145 |
+
cv2.CHAIN_APPROX_SIMPLE)
|
| 146 |
+
cnts = imutils.grab_contours(cnts)
|
| 147 |
+
|
| 148 |
+
# loop over the contours
|
| 149 |
+
|
| 150 |
+
for c in cnts:
|
| 151 |
+
# if the contour is too small, ignore it
|
| 152 |
+
if cv2.contourArea(c) < conf["min_area"]:
|
| 153 |
+
continue
|
| 154 |
+
|
| 155 |
+
# compute the bounding box for the contour, draw it on the frame,
|
| 156 |
+
# and update the text
|
| 157 |
+
(x, y, w, h) = cv2.boundingRect(c)
|
| 158 |
+
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
| 159 |
+
piece_image = frame[y:y+h,x:x+w]
|
| 160 |
+
text = "Piece found"
|
| 161 |
+
# cv2.imshow("Image", image)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
if text == "Piece found":
|
| 165 |
+
# to save images of bounding boxes
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
motionCounter += 1
|
| 169 |
+
print("motionCounter= ", motionCounter)
|
| 170 |
+
print("image_number= ", image_number)
|
| 171 |
+
|
| 172 |
+
# # Save image if motion is detected for 8 or more successive frames
|
| 173 |
+
if motionCounter >= 8:
|
| 174 |
+
image_number +=1
|
| 175 |
+
image_name = str(image_number)+"image.jpg"
|
| 176 |
+
cv2.imwrite(os.path.join(path, image_name), piece_image)
|
| 177 |
+
motionCounter = 0 #reset the motion counter
|
| 178 |
+
|
| 179 |
+
# Open the image, resize it and increase its contrast
|
| 180 |
+
input_image = Image.open('lego-pieces/'+ image_name)
|
| 181 |
+
input_image = ImageOps.grayscale(input_image)
|
| 182 |
+
input_image = input_image.resize((128,128))
|
| 183 |
+
input_data = img_to_array(input_image)
|
| 184 |
+
input_data = increase_contrast_more(input_data)
|
| 185 |
+
input_data.resize(1,128,128,1)
|
| 186 |
+
|
| 187 |
+
# Pass the np.array of the image through the tflite model. This will output a probablity vector
|
| 188 |
+
interpreter.set_tensor(input_details[0]['index'], input_data)
|
| 189 |
+
interpreter.invoke()
|
| 190 |
+
output_data = interpreter.get_tensor(output_details[0]['index'])
|
| 191 |
+
|
| 192 |
+
# Get the index of the highest value in the probability vector.
|
| 193 |
+
# This index value will correspond to the labels vector created above (i.e index value 1 will mean the object is most likely labels[1])
|
| 194 |
+
category_number = np.argmax(output_data[0])
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# Return the classification label of the image
|
| 198 |
+
classification_label = labels[category_number]
|
| 199 |
+
print("Image Label for " + image_name + " is :", classification_label)
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
else:
|
| 204 |
+
motionCounter = 0
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
# check to see if the frames should be displayed to screen
|
| 209 |
+
if conf["show_video"]:
|
| 210 |
+
# display the feed
|
| 211 |
+
cv2.imshow("Feed", frame)
|
| 212 |
+
key = cv2.waitKey(1) & 0xFF
|
| 213 |
+
# if the `q` key is pressed, break from the lop
|
| 214 |
+
if key == ord("q"):
|
| 215 |
+
break
|
| 216 |
+
# clear the stream in preparation for the next frame
|
| 217 |
+
rawCapture.truncate(0)
|
rpi-object-detection/rpi_requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencv-contrib-python==4.5.3.56
|
| 2 |
+
picamera== 1.13
|
| 3 |
+
tflite-runtime == 2.9.1
|
| 4 |
+
Pillow >= 9.0.1
|
| 5 |
+
numpy == 1.23.2
|