本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

基于树莓派的智能汽车

发布时间:2021-12-25
分享到:

基于树莓派的智能汽车

发布时间:2021-12-25
分享到:

使用树莓派让你的“哑巴”汽车更智能!

灵感
我喜欢智能汽车的想法,但我很难证明购买一辆全新的汽车只是为了获得一些花里胡哨的东西。目前,我被我的“哑巴”车困住了,但这并不意味着我不能自己尝试让它变得更聪明!

“智能汽车”这个词可以有几十种不同的含义,这取决于你问的是谁。那么让我们从我对智能购物车的定义开始:

  • 触摸屏界面
  • 倒车摄像头可让您知道物体是否太近
  • 有关汽车的基本信息,例如燃油效率
  • 也许蓝牙连接?

我不确定这些项目中的哪些(如果有)我会取得成功,但我想我们会知道的。

备用相机

我们想要的智能汽车的第一个明显补充是备用摄像头。有许多套件可以让添加备用摄像头变得非常简单。但是他们中的大多数需要对汽车本身进行修改,而且由于我只是想测试概念验证,我真的不想开始拧开并钻入我的汽车。

像这样的相机的一个警告是它们需要外部电源。通常规定将它们连接到您汽车的倒车灯之一,以便在汽车倒车时自动打开电源。由于我现在不想改装我的车,我只是打算将它连接到一些电池上。我会用可靠的旧胶带将它安装到车牌上!

我将 RCA 电缆从相机连接到仪表板并将其连接到我的 5' LCD。这个特定的 LCD 可以通过 USB 供电,所以我将它插入了 USB 打火机适配器(大多数旧车都有打火机适配器)。

启动汽车后,屏幕立即亮起,我可以看到相机的图像。像宣传的那样工作!对于只想在他们的汽车上添加备用摄像头并且不希望它有任何花里胡哨的人来说,这将是一个很好的解决方案。不过,我认为我可以做得更好。

进入树莓派。Pi 是智能汽车的完美平台,因为它基本上是一台具有大量输入和输出的微型计算机。将相机连接到 Pi 时,您几乎可以使用任何通用 USB 网络摄像头,或者您可以使用Pi Camera 。这两款相机都不需要单独的电源。但只要确保您有足够的电缆连接到汽车后部即可。

我选择了 Pi 相机,因为它比 USB 相机具有更高的吞吐量。同样,我只是将相机用胶带粘在车牌上,将扁平电缆连接到汽车前部的 Pi,然后将其连接到 7" 触摸屏。Pi 和触摸屏都可以通过 USB 适配器供电在车里。

打开汽车,树莓派和屏幕都通电了。一个明显的缺点是 Pi 启动所需的启动时间……稍后我将不得不考虑这一点。为了查看 Pi cam,我打开了终端并运行了一个简单的脚本(将来可以设置为自动启动的脚本)

按回车键后,弹出了一个摄像机的提要!Pi 上的视频的好处在于您可以对其进行分析,甚至可以在物体离得太近时设置警报系统!那么接下来让我们继续努力吧!

物体检测

方法一

说到商用备用摄像机,我看到的一般有两个版本。第一个使用带有颜色范围的静态叠加,以便您可以直观地确定对象的接近程度。第二种方法是将摄像头与某种类型的传感器结合使用,可以感应物体与汽车的距离,然后在物体太近时提醒您。

由于第一种方法似乎更容易,让我们先尝试一下。从本质上讲,它只是在视频流顶部过度播放的图像,所以让我们看看重新创建它是否像听起来一样简单。我们需要的第一件事是透明的图像叠加。

上面的图像正好是 640x480,恰好与我的相机将流式传输的分辨率相同。这是有意完成的,但如果您以不同的分辨率流式传输,请随时更改图像尺寸。

接下来,我们将创建一个使用 PIL python 图像编辑器和 PiCamera 的 Python 脚本(如果您没有使用 Pi 相机,则调整视频输入的代码)。我只是将文件命名为image_overlay.py

保存它并通过运行“python image_overlay.py”对其进行测试,我使用玩具车对其进行了小规模测试,以了解其工作原理。

将其装入车内并运行程序,结果同样迷人!然而,需要注意的一件非常重要的事情是,您在校准相机时应该特别小心,以确保视频视图的底部尽可能靠近汽车保险杠。如下图所示,相机朝向太高,因此测试对象实际上比相机告诉我的要远得多。

方法二

方法 1 测试非常成功,但它也非常基础。如果有一个系统可以检测物体与汽车的距离并在距离太近时通知您,那就太好了。正如我之前提到的,大多数具有该功能的汽车都使用可以检测物体的外部传感器。我不太热衷于在我的汽车上添加任何其他外部设备,所以我要看看我是否可以使用计算机视觉检测物体。

我可以使用OpenCV和 python 作为我的计算机视觉 API。这将使我能够分析图像中的内容并为找到的内容设置参数。因此,我们的想法是拍摄视频片段并在底部(靠近汽车保险杠)设置“警报区域”的边界。然后我会让它检测镜头中的任何大物体。如果对象的最底部区域进入“警报区域”,则应发送警报消息。

作为警报声,我将通过将正极连接到引脚 22,将负极连接到接地引脚,将压电蜂鸣器连接到 Raspberry Pi。

在开始编写代码之前,我们必须先在 Pi 上安装 OpenCV。幸运的是 Pi 可以通过 Python 的“pip”命令来做到这一点

一旦安装了 OpenCV,我们就可以创建一个新的 Python 文件并开始编写代码。对于完整记录的代码,您可以访问下方的链接。我只是将我的文件命名为car_detector.py

import time
import cv2
import numpy as np
from picamera.array import PiRGBArray
from picamera import PiCamera
import RPi.GPIO as GPIO
buzzer = 22
GPIO.setmode(GPIO.BCM)
GPIO.setup(buzzer, GPIO.OUT)
camera = PiCamera()
camera.resolution = (320, 240) #a smaller resolution means faster processing
camera.framerate = 24
rawCapture = PiRGBArray(camera, size=(320, 240))
kernel = np.ones((2,2),np.uint8)
time.sleep(0.1)
for still in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
    GPIO.output(buzzer, False)
    
    image = still.array
    #create a detection area
    widthAlert = np.size(image, 1) #get width of image
    heightAlert = np.size(image, 0) #get height of image
    yAlert = (heightAlert/2) + 100 #determine y coordinates for area
    cv2.line(image, (0,yAlert), (widthAlert,yAlert),(0,0,255),2) #draw a line to show area
    
    lower = [1, 0, 20]
    upper = [60, 40, 200]
    lower = np.array(lower, dtype="uint8")
    upper = np.array(upper, dtype="uint8")
    #use the color range to create a mask for the image and apply it to the image
    mask = cv2.inRange(image, lower, upper)
    output = cv2.bitwise_and(image, image, mask=mask)
    
    dilation = cv2.dilate(mask, kernel, iterations = 3)
    closing = cv2.morphologyEx(dilation, cv2.MORPH_GRADIENT, kernel)
    closing = cv2.morphologyEx(dilation, cv2.MORPH_CLOSE, kernel)
    edge = cv2.Canny(closing, 175, 175)
    
    contours, hierarchy = cv2.findContours(closing, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    threshold_area = 400
    centres = []
    
    if len(contours) !=0:
        
        for x in contours:
            #find the area of each contour
            area = cv2.contourArea(x)
            #find the center of each contour
            moments = cv2.moments(x)
            #weed out the contours that are less than our threshold
            if area > threshold_area:
                
                (x,y,w,h) = cv2.boundingRect(x)
                
                centerX = (x+x+w)/2
                centerY = (y+y+h)/2
                
                cv2.circle(image,(centerX, centerY), 7, (255, 255, 255), -1)
                
                if ((y+h) > yAlert):
                    cv2.putText(image, "ALERT!", (centerX -20, centerY -20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255),2)
                    GPIO.output(buzzer, True)
    
    cv2.imshow("Display", image)
    
    rawCapture.truncate(0)
    
    key = cv2.waitKey(1) & 0xFF
    if key == ord("q"):
        GPIO.output(buzzer, False)
        break

好的,保存并小规模测试,效果很好。它检测到很多不必要的物体,我确实注意到有时它会将阴影检测为物体。

将其装入汽车并在真实世界场景中进行测试,结果出人意料地准确!然而,这是接近完美的条件。如果是在晚上,我不知道结果会如何变化。

总的来说,我对结果感到高兴和惊讶,但我不会在不太理想的条件下相信它。这并不是说它不起作用,只是说它目前是基本代码,可以进行更多的测试和调试(希望读者!)

在这两种方法中,方法 2 很酷,但方法 1 在多种情况下肯定更可靠。所以如果你要为你的车做这个,我会选择方法 1。

接下来,我将尝试连接到汽车的 OBDII 端口,看看我能提取什么!

连接到车载诊断 (OBDII)
在美国,自 1996 年以来,汽车一直被要求具有车载诊断端口 (OBDII)。其他国家稍后采用了相同的标准。

OBDII 端口允许您连接到它并读取有关汽车的信息,例如问题、VIN 号码、速度、RPM 等。每当您的“检查引擎”灯亮起时,机械师就会插入它以找出问题所在是并清除问题。

我们需要做的第一件事是将适配器连接到端口,以便我们可以从 Raspberry Pi 与其通信。对于那些不知道的人,在大多数汽车中,OBDII 端口位于方向盘下方的仪表板下方。如果您在网上搜索“ODBII 适配器”,您会发现主要有两种类型的适配器:USB 和蓝牙。USB 适配器更安全,但如果您了解蓝牙中的漏洞以及如何解决这些漏洞,则可以使用该适配器。

将蓝牙适配器连接到端口,您可能会注意到适配器上的灯立即亮起。这是因为 OBD 端口具有“始终开启”的 12v 输出。这意味着蓝牙适配器将始终处于供电和活动状态,即使您不在车内......因此存在漏洞。

蓝牙适配器就位后,我们现在可以将我们的 Pi 连接到它。我一直在使用内置蓝牙的 Raspberry Pi 3 B+,所以我不需要任何其他适配器。只需打开 pi,打开终端并启动蓝牙控制器。

在控制器中,按顺序输入这些命令(减去 # 注释)

此时,适配器应该已连接,您应该回到主终端线路。由于 OBD 端口是一个串口,在我们开始与它通话之前,我们需要将它绑定到 Pi 上的一个串口。

第二组值用于参数 ID。有将近 200 种不同的可用 PID,同样可以在这个 Wikipedia 页面上找到。在这里我们可以询问温度、速度、RPM 等。对于这个测试,我将要求速度,所以我的十六进制值为 0D。

制作图形界面
现在我们有了数据,我们需要一种更好、更有趣的方式来显示它。由于我最熟悉 Python,因此我将使用它来操作数据。在网上,我找到了一个专门用于 OBD 连接的很棒的库,称为Python-OBD 。所以我和 PySerial 一起安装了它。

好吧,python-OBD 库可以工作,但它仍然不是图形化的。要在 Python 中制作图形界面,有几个选项可供选择。我熟悉的几个是:

  • PyQT
  • 特金特
  • 游戏

Pygame 既快速又简单,因此我将在本教程中使用它。更方便的是,Pygame 预装在树莓派上,所以无需额外安装任何东西!

好吧,让我们试一试。这是我用于显示的最终代码

obd_hud_test.py

import pygame
from pygame.locals import *
import obd
pygame.init()
#connection = obd.OBD()
connect = obd.Async(fast=False)
screen = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
screen_w = screen.get_width()
screen_h = screen.get_height()
circle_y = screen_h/2
circle1_x = screen_w * .25
circle2_x = screen_w * .5
circle3_x = screen_w * .75
circle_rad = (circle2_x - circle1_x)/2
speed_text_x = screen_w * .25
speed_text_y = screen_h * .25
rpm_text_x = screen_w * .5
rpm_text_y = screen_h * .25
load_text_x = screen_w * .75
load_text_y = screen_h * .25
headerFont = pygame.font.SysFont("Arial", 50)
digitFont = pygame.font.SysFont("Arial", 50)
white = (255,255,255)
black = (0,0,0)
grey = (112, 128, 144)
speed = 0
rpm = 0
load = 0
def draw_hud():
    screen.fill(grey)
    pygame.draw.circle(screen, black, (int(circle1_x), int(circle_y)), int(circle_rad), 5)
    pygame.draw.circle(screen, black, (int(circle2_x), int(circle_y)), int(circle_rad), 5)
    pygame.draw.circle(screen, black, (int(circle3_x), int(circle_y)), int(circle_rad), 5)
    speed_text = headerFont.render("SPEED", True, black)
    rpm_text = headerFont.render("RPM", True, black)
    load_text = headerFont.render("LOAD", True, black)
    speed_text_loc = speed_text.get_rect(center=(speed_text_x, speed_text_y))
    rpm_text_loc = rpm_text.get_rect(center=(rpm_text_x, rpm_text_y))
    load_text_loc = load_text.get_rect(center=(load_text_x, load_text_y))
    screen.blit(speed_text, speed_text_loc)
    screen.blit(rpm_text, rpm_text_loc)
    screen.blit(load_text, load_text_loc)
def get_speed(s):
    global speed
    if not s.is_null():
        #speed = int(s.value.magnitude) #for kph
        speed = int(s.value.magnitude * .060934) #for mph
def get_rpm(r):
    global rpm
    if not r.is_null():
        rpm = int(r.value.mangitude)
def get_load(l):
    global load
    if not l.is_null():
        load = int(l.value.mangitude)
connection.watch(obd.commands.SPEED, callback=get_speed)
connection.watch(obd.commands.RPM, callback=get_rpm)
connection.watch(obd.commands.ENGINE_LOAD, callback=get_load)
connection.start()
running = True
while running:
    for event in pygame.event.get():
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                connection.stop()
                connection.close()
                running = False
            elif event.type == QUIT:
                connection.stop()
                connection.close()
                running = False
    draw_hud()
    speedDisplay = digitFont.render(str(speed), 3, white)
    rpmDisplay = digitFont.render(str(rpm), 3, white)
    loadDisplay = digitFont.render(" " + str(load) + " %", 3, white)
    screen.blit(loadDisplay, (circle3_x-(circle3_x/8), circle_y-45))
    screen.blit(rpmDisplay, (circle2_x-(circle2_x/8), circle_y-45))
    screen.blit(speedDisplay,(circle1_x-(circle1_x/8), circle_y-45))
    pygame.display.update()
    pygame.display.flip()

添加 GPS
尽管大多数智能设备已经集成了 GPS,但我认为专用的 GPS 导航系统将是我们智能汽车设置的完美补充。

常见的 GPS 系统通常由两部分组成,一个是 GPS 加密狗,用于从卫星检索坐标,另一个是将这些坐标覆盖在地图上的地图绘制系统。对于 Raspberry Pi,您可以使用 USB 加密狗,或通过 Pi 的 GPIO 引脚连接的加密狗。你可以花 10 到 40 美元买到一个好的。只需确保您选择的加密狗与 Raspberry Pi 兼容。由于我已经有一个 USB GPS 加密狗,我将使用它。

在大多数情况下,插入 GPS 应该可以工作,但对我来说,我必须进行一些故障排除才能使其正常工作。首先我必须安装一些 GPSD 包,然后我运行“cgps -s”来检查连接状态。

原来的:

更改后:

保存,并重新启动GPS服务,我再次测试,终于开始看到结果!

现在是地图系统。大多数移动地图系统需要无线数据连接才能下载地图。但是为 Raspberry Pi 获得无线数据连接是我不想承担的每月额外费用。因此,在网上寻找与 Raspberry Pi 兼容的离线地图系统时,没有太多选择。真的,唯一可行的方法是Navit 。

如果您对此项目有任何想法、意见或问题,请在下方留言。

原文链接丨以上内容来源网络,如涉及侵权可联系删除。

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论