导航

    全志在线开发者论坛

    • 注册
    • 登录
    • 搜索
    • 版块
    • 话题
    • 在线文档
    • 社区主页

    花费200元,我用雪糕棒手搓了一台可UI交互的视觉循迹小车

    H/F/TV Series
    1
    1
    939
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • q1215200171
      budbool LV 9 最后由 编辑

      常见的视觉循迹小车都具备有路径识别、轨迹跟踪、转向避障、自主决策等基本功能,如果不采用红外避障的方案,那么想要完全满足以上这些功能,摄像头、电机、传感器这类关键部件缺一不可,由此一来小车成本也就难以控制了。

      但如果,有这样一款视觉循迹小车,它可以完全自己手搓,并用成本极低的雪糕棒来搭建车体架构,不仅保留了传统循迹小车具备的所有功能,还额外适配上一块小屏幕并配上UI界面用于升级人机交互方式。

      小车运动.gif

      UI交互.gif

      更重要的是,它的器件成本被压缩到200元左右,这样的视觉循迹小车能让你心动吗~

      C27FD20B-31CB-4949-90FD-4F327EB3EC9A.png

      核桃派视觉循迹小车简介

      核桃派H616视觉循迹小车的循迹功能和人机交互界面整体代码由Python+Qt实现,它通过摄像头获取周围环境的图像信息,并利用图像处理算法识别出特定的标记或路径,然后根据标记或路径的形状和方向信息,自动控制小车的行驶方向和速度,以实现沿着预定轨迹自动行驶的目的。

      手搓.gif

      手搓一台视觉循迹小车所需要用到的基础硬件材料如下:

      1、核桃派H616开发板+LCD屏幕≈178元;
      2、四个电机+车轮≈16元;
      3、电机驱动模块≈4元;
      4、摄像头≈50元;
      5、移动电源≈20元;
      6、雪糕棒若干≈4元(也可以≈不要钱);

      循迹功能实现

      要让小车实现循迹自运动的操作,其实也可以说是一个在教小车如何精准识别线路并做出判断的过程,想要小车的摄像头实现对路线的准确判断,就需要用到一个目前循迹小车最广泛采用的技术手段——二值化。

      UI识别.gif

      二值化是图像分割的一种方法,用于将图像中的像素点矩阵的灰度值设置为0或255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。

      在二值化过程中,将大于某个临界灰度值的像素灰度设为灰度极大值(通常是255),将小于这个值的像素灰度设为灰度极小值(通常是0),从而实现二值化。

      # 根据不同模式,用不同的hsv上下限值
      upper_hsv = (180,255,100)
      lower_hsv = (0,0,0)
      grayImage = cv2.inRange(hsvImage, np.array(lower_hsv), np.array( upper_hsv)) # 颜色二值化
      

      小车识别.png

      二值化图像后,整个画面会被区分为黑白分明的两种颜色,之后就需要进行路线轮廓的描绘以及质心的标注,这个操作的目的是让小车知道该往左拐还是往右拐,进而控制两边车轮的速度。

      • 获取最大轮廓
      # 获取所有轮廓,画出所有轮廓
      contours, hierarchy = cv2.findContours(grayImage, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
      areas = [cv2.contourArea(c) for c in contours]]
      cv2.drawContours(rgbImage,contours, -1,(0,255,0),3)
      
      • 计算质心
      # 计算面积最大轮廓的质心
      areas = [cv2.contourArea(c) for c in contours]
         try:
            M = cv2.moments(contours[areas.index(max(areas))])
         except:
            pass
         M10=M.get("m10")
         M01=M.get("m01")
         M00=M.get("m00")
         if M00 <= 0 :
            continue
         cX = int(M10 / M00)
         cY = int(M01 / M00)
      # 绘制质心
      cv2.circle(rgbImage, (cX, cY), 15, (255, 0, 255), -1)
      

      计算质心.png

      在绘制轮廓与质心后就需要进行质心坐标的判断,这里的原理很简单,就是质心偏左就往左转,质心偏右就往右转,在判断的同时通过电机来控制两边车轮的速度进而控制小车的行驶方向。

      1月29日.png
      1月29日(2).png
      1月29日(1).png

      根据质心的位置计算车轮速度的控制参数postion,根据具体的计算公式可以得知,postion的取值范围为-50到+50,其中0代表质心在正中间,+越大质心越往左,-越大质心越往右,则进行下面的速度计算和控制,否则将速度设置为0,再根据postion和delta_sum的值以及其他参数的调整,计算并控制左右车轮的速度。

      
      # 车轮速度控制
      if self.flag_start.status() == True :
          # -50 ~ +50  :0为正中间,+越大则越往左,-越大则越往右,
          postion = int(int(center_x - cX) / int(center_x / 50)) 
          # postion += self.delta_sum 
      
          step = self.postion_last - postion
          self.delta_sum += (postion - step)*0.01
          if self.delta_sum > 100:
              self.delta_sum =  100
          elif self.delta_sum < -100:
              self.delta_sum =  -100
          if abs(postion) < 5 :
              self.delta_sum = 0
             
          # print("self.delta_sum", self.delta_sum)
          self.postion_last = postion
               
          speed_l = 50 - postion - int(self.delta_sum) 
          speed_r = 50 + postion + int(self.delta_sum) 
      
          motor.L.speed(speed_l)
          motor.R.speed(speed_r)
          self._slider_l.setValue(speed_l)
          self._slider_r.setValue(speed_r)
      

      摄像头二值化识别.gif

      人机交互界面

      核桃派H616开发板上预装了PyQt,所以可以使用Qt自带的设计器软件来画窗口,在设计好后通过命令一键转化为Python代码,再去核桃派的开发文档复制一段显示案例的代码,就可以轻松在电脑上预览到刚刚的窗口画面。

      UI交互.gif

      为了在远程服务器上运行图形界面应用程序,通过设置os.environ["DISPLAY"] = ":0.0"允许Thonny远程运行。

      # 允许Thonny远程运行
      import os
      os.environ["DISPLAY"] = ":0.0"
      

      定义了一个名为event_press的函数,用于处理QPushButton按钮的released事件。当按钮被释放时,切换work.flag_start的状态,并根据状态改变按钮的文本。

      def event_press():
          if work.flag_start.status():
              work.flag_start.disable()
              ui.pushButton.setText("点击开始")
          else :
              work.flag_start.enable()
              ui.pushButton.setText("点击结束")
      

      UI绘制.gif

      为了处理三个不同按钮的released事件,定义了change_to_mode三个函数,这些函数用于将work.flag_mode的模式设置为不同的值。

      
      def change_to_mode0():
          work.flag_mode.set_mod( 0 )
      def change_to_mode1():
          work.flag_mode.set_mod( 1 )
      def change_to_mode2():
          work.flag_mode.set_mod( 2 )
      ui.pushButton.released.connect(event_press)
      ui.pushButton_auto.released.connect(change_to_mode0)
      ui.pushButton_black.released.connect(change_to_mode1)
      ui.pushButton_white.released.connect(change_to_mode2)
      

      以上这些代码片段是构成一个由PyQt所创建GUI的关键部分。它通过创建一个窗口,并在窗口中显示了一些UI元素,同时定义了一些事件处理函数,这个应用程序根据用户的操作来控制某些功能,并使用定时器来让解释器每隔一段时间运行一次,以保持界面响应性能,最后进入主循环等待事件的触发和处理。

      开源资料获取

      本文所有内容均转载自原作者本人的B站视频账号及核桃派论坛开源文章,文章内所提到的小车部件和源代码均公开在帖子和视频中,感兴趣的小伙伴可以复制下方链接或者戳文末的“阅读原文”获取。

      • 原文及源代码获取:https://forum.walnutpi.com/t/topic/116

      • B站视频链接:https://www.bilibili.com/video/BV1TK411i7Bb/?share_source=copy_web&vd_source=6ec797f0de1d275e996fb7de54dea06b

      7A5BA766-BD67-4aa0-AE15-0FB6A01A01C5.png

      1 条回复 最后回复 回复 引用 分享 0
      • 1 / 1
      • First post
        Last post

      Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号

      行为准则 | 用户协议 | 隐私权政策