在Nullspace Robotics实习期间,我有幸参与了一个可以增强公司能力的项目。我们将物体检测和机器学习图像识别集成起来,开发了一台能够实时分类乐高技术积木的机器。
在本博客文章中,我将指导您了解我们遇到的挑战以及我们如何成功地将该项目实现。
Amos Koh和我在Nullspace期间,在教授学生编程和机器人技术的同时,致力于这个项目。您可以在文章下方的链接中找到我们。
Nullspace Robotics是新加坡领先的提供小学和中学学生机器人和编程教育的机构。他们的运营中有很大一部分是使用乐高技术积木构建机器人,并将其分类放入特定的托盘中。可以想象,在让一个充满无限精力的8岁孩子帮忙将积木放回托盘时,这是一项让人困扰的任务,因为他们只想继续构建更多东西。
Nullspace要求我们制作一台可以将乐高技术积木按照特定类别进行排序的机器,并尽量减少人类干预,以解决机器人课程中的关键效率问题
定义挑战
该项目涉及3个主要部分:实时物体和运动检测、图像识别以及机器硬件的构建。由于实习时间限制,我们主要集中在前两个方面,即项目的软件部分。
一个关键挑战是识别运动部件,并在同一帧内对其进行定位。我们考虑了两种方法:将机器学习图像识别集成到物体检测摄像头中,或将这些过程分开进行。
最终,我们决定分开物体检测和识别。这种方法首先涉及在检测到物体后捕获合适的图片,然后运行模型对图像进行分类。将这些过程集成到一起将需要在几乎每一帧上运行模型,以对每个检测到的物体进行分类。将它们分开消除了模型需要处于持续处理模式的必要性,确保了更平滑和更高效的操作。
物体检测
我们使用了文章下方引用的项目中的思路来实现我们的物体/运动检测程序,并将其定制为乐高积木。
在我们的案例中,我们使用了类似的运动检测概念,因为我们的机器将涉及一个颜色均匀的传送带系统,因此检测到的任何运动都将是由于乐高积木在传送带上移动。
我们对所有帧应用了高斯模糊和其他图像处理技术,并将其与先前的帧进行比较。然后进行进一步处理,以隔离(在其周围绘制边界框)引起运动的物品,如下所示:
for f in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): frame = f.array # grab the raw NumPy array representing the image text = "未发现物体" # initialize the occupied/unoccupied text # resize the frame, convert it to grayscale, and blur it frame = imutils.resize(frame, width=500) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (21, 21), 0) # if the average frame is None, initialize it if avg is None: print("[INFO] starting background model...") avg = gray.copy().astype("float") rawCapture.truncate(0) continue # accumulate the weighted average between the current frame and # previous frames, then compute the difference between the current # frame and running average cv2.accumulateWeighted(gray, avg, 0.5) frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg)) # threshold the delta image, dilate the thresholded image to fill # in holes, then find contours on thresholded image thresh = cv2.threshold(frameDelta, conf["delta_thresh"], 255, cv2.THRESH_BINARY)[1] thresh = cv2.dilate(thresh, None, iterations=2) cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) # loop over the contours for c in cnts: # if the contour is too small, ignore it if cv2.contourArea(c) < conf["min_area"]: continue # compute the bounding box for the contour, draw it on the frame, # and update the text (x, y, w, h) = cv2.boundingRect(c) cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) piece_image = frame[y:y+h,x:x+w] text = "发现物体" # cv2.imshow("Image", image)
为了确保运动是由乐高积木引起的,我们使用运动计数器评估了运动检测的稳定性,该计数器检查在确定运动实际上是由乐高积木而不是杂音引起之前,在一定数量的帧中检测到运动。然后保存最终图像并将其输入我们的卷积神经网络进行分类。
if text == "Piece found": # 保存边界框的图像 motionCounter += 1 print("motionCounter= ", motionCounter) print("image_number= ", image_number) # 如果连续检测到运动的帧数超过等于8,则保存图像 if motionCounter >= 8: image_number +=1 image_name = str(image_number)+"image.jpg" cv2.imwrite(os.path.join(path, image_name), piece_image) motionCounter = 0 #重置运动计数器 #使用我们的模型对保存的图像进行分类,详见下文
创建模型
构建数据集
我们自己创建了图片数据集,而不是使用在网上找到的乐高技术积木的图像,因为我们希望复制最终模型将检测和分类积木时使用的条件。因此,我们设计了一个简单的传送带系统,使用的是乐高技术积木本身!然后我们将其连接到一个乐高Spike Prime电机,以保持传送带运动。
设计模型架构
为了解决挑战的核心问题,我对Aladdinpersson的GitHub存储库上找到的一个机器学习模型进行了修改。这个模型具有一系列从128到64到32到16的卷积层,这是为了提高图像识别的架构选择。
我们没有使用预训练模型,而是设计了自己的卷积神经网络,原因如下:
- 我们不需要对图像进行特别深入的特征提取
- 我们希望保持模型的小型化和简化,同时减少运行模型的计算成本。这将使我们能够在树莓派上更高效地运行CNN作为tflite模型。
数据标准化是一个关键步骤,以确保一致的训练准确性,尤其是由于不同图像由于光照差异而捕捉到的值范围的变化。
在这个模型中,ReLU、dense、softmax和flatten等各个层起着关键作用。例如,ReLU激活对图像分类至关重要,因为它解决了图像识别中梯度消失的问题。另一方面,密集层在Tensorflow模型中是标准的,方便密集连接的神经网络。我们使用Softmax激活来计算数据集中每个类别的概率。
对于损失函数,我们使用了Keras的Sparse Categorical Cross Entropy,这是一个适合多类别分类任务的选择。我们使用Keras Adam优化器来微调模型。
训练和优化
我们精心选择了训练和过拟合之间的平衡的迭代次数,优先选择少于200次,以确保模型性能最佳。为了加速模型训练,我们利用了谷歌Colab,它提供了GPU资源,确保训练速度比我们自己的笔记本电脑要快得多。
完整的模型架构如下所示:
data_augmentation = keras.Sequential([ layers.RandomFlip("horizontal", input_shape=(img_height, img_width, 1)), layers.RandomRotation(0.2), layers.RandomZoom(0.1), ])model = keras.Sequential( [ data_augmentation, layers.Rescaling(1./255, input_shape = (img_height,img_width,1)), #规范化数据输入 layers.Conv2D(128, 3, padding="same", activation='relu'), layers.MaxPooling2D(pool_size=(2,2)), layers.Conv2D(64, 3, padding="same", activation='relu'), #这个应该是16还是32个神经元?尝试使用更多数据 layers.MaxPooling2D(pool_size=(2,2)), layers.Conv2D(32, 3, padding="same", activation='relu'), layers.MaxPooling2D(pool_size=(2,2)), layers.Conv2D(16, 3, padding="same", activation='relu'), layers.MaxPooling2D(pool_size=(2,2)), layers.Dropout(0.1), layers.Flatten(), layers.Dense(10,activation = 'relu'), layers.Dense(7,activation='softmax'), #输出类别数 ]) model.compile( optimizer=keras.optimizers.Adam(), loss=[keras.losses.SparseCategoricalCrossentropy(from_logits=False),], metrics=["accuracy"],)model_history = model.fit(x_train, y_train, epochs=200, verbose=2, validation_data=(x_test,y_test), batch_size=25) #我认为25/32是最好的批大小
模型结果
该模型使用6000张图像对乐高科技积木的7个类别进行训练。最终验证准确率达到93%。以下是展示训练进展的图表,以及用于评估性能的混淆矩阵:
在树莓派上实施模型
在树莓派上运行神经网络的最高效方法是作为 tflite(tensorflow lite)模型。我们将模型保存在本地,然后加载到树莓派上。
from tflite_runtime.interpreter import Interpreter# 加载 TFLite 模型并分配张量.interpreter = Interpreter(model_path="lego_tflite_model/detect.tflite") # 插入 tflite 模型的路径interpreter.allocate_tensors()
从上述运动计数器的循环中继续,然后将合适的图像输入神经网络进行分类:
# 从 if text == "Piece found": 继续 # 打开图像,调整大小并增加对比度 input_image = Image.open('lego-pieces/'+ image_name) input_image = ImageOps.grayscale(input_image) input_image = input_image.resize((128,128)) input_data = img_to_array(input_image) input_data = increase_contrast_more(input_data) input_data.resize(1,128,128,1) # 将图像的 np.array 通过 tflite 模型进行处理。这将输出一个概率向量 interpreter.set_tensor(input_details[0]['index'], input_data) interpreter.invoke() output_data = interpreter.get_tensor(output_details[0]['index']) # 获取概率向量中最高值的索引。 # 此索引值将对应于上面创建的标签向量(即索引值1表示该对象最有可能是 labels[1]) category_number = np.argmax(output_data[0]) # 返回图像的分类标签 classification_label = labels[category_number] print("图像 " + image_name + " 的标签是:", classification_label) else: motionCounter = 0 # 重置运动计数器以寻找新的对象
灵活性是一个重要考虑因素。运动计数器可以根据拍摄图像来构建数据集或设置分类的阈值,增强系统的多功能性。
展示
我们的努力的最终体现是该系统的整体准确性,通过照片和视频记录其操作来支持。传送带设置(如上图)是此演示的重要组成部分:
未来工作和改进领域
软件:该模型无疑会从质量更高的相机获益。此外,未来的扩展还包括在操作中包含一个质量检查器模型,以确保用于分类零件的图像合适。
硬件:我们为测试和演示临时构建的传送带系统需要进行扩展,以容纳更多零件。还需要制定和实施一种方法,以将多个乐高积木分离,并确保相机画面中只显示一个零件。有类似的在线项目详细介绍了可能的方法。
结论
我在Nullspace Robotics的旅程是我首次尝试为实际目的构建自己的神经网络。过去,在培训课程中设计模型是一个完全不同的体验,现在我们需要考虑各种因素,如资源、用途以及如何调整数据集和模型以适应我们的目的。我期待继续我的机器学习之旅,并利用最新的AI技术构建更多创新解决方案。
我想感谢Nullspace提供这个项目的机会,期待看到公司在推动机器人教育领域的进一步突破。
在Github或HuggingFace上查看完整的代码库、数据集图像和有关项目的更多信息:
Github:https://github.com/magichampz/lego-sorting-machine-ag-ak/
HuggingFace:https://huggingface.co/magichampz
开发者介绍
Aveek:https://www.linkedin.com/in/aveekg00/
Amos:https://www.linkedin.com/in/ak726/
参考资料:
使用Python和OpenCV进行基本运动检测和跟踪- PyImageSearch
在本教程中,我将向您展示如何使用Python和OpenCV进行基本运动检测和跟踪。学习如何…
pyimagesearch.com
使用OpenCV进行运动检测-初学者的图像分析
如何使用OpenCV检测和分析运动物体
towardsdatascience.com
Machine-Learning-Collection/ML/TensorFlow/Basics/tutorial15-customizing-modelfit.py at master ·…
一个关于机器学习和深度学习的学习资源…
github.com
花絮
除非另有声明,所有图片均为作者所拍摄。