草庐IT

基于yolov5的目标检测和单目测距

Lange小蓝子 2023-06-06 原文

废话在前头

  因为我的毕业设计就是搞目标检测这一块的,前段时间好不容易实现了yolov5的目标检测,后来还需要实现目标测距,于是在B站和博客里不断摸索,效果很不理想,知识点零散,幸运的是最终琢磨懂了代码和原理。全文通俗易懂。

正文开始

1、最终目的

  实现目标检测跟踪和目标测距 !

2、实现过程

2.1 实现的前提

  前提是你要会用yolov5实现目标检测,我是跟着一位博主学做出来的——目标检测—教你利用yolov5训练自己的目标检测模型 。重点是在B站里也有他做的 保姆级视频本文着重讲解单目测距。 实现了yolov5检测之后直接去我的gitee上下载我的测距项目: yolov5-main。下载并解压项目到PyCharm中,配置上运行yolov5的环境,然后跑一遍detect.py,看看我的案例效果,成功运行后再跟着教程慢慢搞。

2.2 知识点简要梳理

  因为条件的限制,我没有选择双目测距来研究,其实单目和双目各有优缺点。它们的原理网上搜也是一大把,本文直接跳过原理讲解,我们来讲公式:

D = (F*W)/P
其中D是目标到摄像机的距离(最终要推测的距离), F是摄像机焦距(根据下面教程来调试自己的镜头焦距), W是目标的宽度或者高度(看情况来定,行人检测一般以人的身高为基准), P是指目标在图像中所占据的x方向像素(宽)或者y方向像素(高)(程序来计算)。

2.3 单目测距代码

  先来看看核心代码

foc = 1810.0        # 镜头焦距
real_hight_person = 64.96   # 行人高度,注意单位是英寸

# 自定义函数,单目测距
def detect_distance_person(h):
    dis_inch = (real_hight_person * foc) / (h - 2)
    dis_cm = dis_inch * 2.54
    dis_cm = int(dis_cm)
    dis_m = dis_cm/100
    return dis_m

简单理解一下代码,定好焦距(通过下一步调试可得)和行人高度(这里定一般人的身高,我定165cm,可以调,单位为英寸),然后在调用这个测距函数的时候,把行人所占据的像素高度h(其实就是利用到方框的像素高度)传进来,这个函数首先会根据公式计算出单位为英尺的距离,然后转换,最终得到单位为米的距离。

2.4 调试焦距

网上有说相机标定这个操作,我没有深入研究,貌似可以矫正图片,减小误差。我是直接跳过这个操作,影响不大。
下面讲一下怎么调试焦距,我用的是手机摄像头。

  1. 先是准备一张A4纸,横放在地板上或者别的东西上面,要纯色为背景;
  2. 用卷尺量出60cm的高度,或者用棍子画出60cm的高度,将手机打开录像,横拍,这个时候就不要放大镜头了,垂直方正的将A4纸录十几秒的视频,拍到的A4纸尽量不能歪;
  3. 将视频放到项目的test目录里,在detect_A4.py的第13行代码换上这个视频。运行detect_A4.py,首先看一下有没有绿色的矩形出现并包裹住A4纸,如果没有或者有但是明显包裹不紧的话就调节25行代码的阈值,反复尝试,直到矩形出现并包裹紧A4纸。这个时候可以看到图像左上角有数值,这个就是A4纸到镜头的推测距离,我们需要通过调节foc焦距参数,让左上角这个数值接近60cm,最终的foc焦距就是你这个视频的焦距了,往后检测视频就用这个焦距,而且检测的视频都是用这个焦距拍出来的。

2.5 拿到焦距就可以做目标测距了

2.5.1 讲一下总体思路

这个时候公式 D = (F*W)/P 的三个参数已经知道了F和W两个,这个P在下面2.的(4)里可以被计算出来。

  1. 我在utils目录下定义了一个distance.py, 它的功能就是测距,我在里边定义了两个测距模型,一个是测人的,一个是测车的,这个时候先把之前调试好的焦距写在foc参数里,行人和车的高度只是大概数值,单位为英寸,自己可以改
  2. 然后来到根目录下的detect.py,这个文件我在基础上做了修改,讲一下改了哪些。
    (1)第60行定义了一个函数,以1200为宽的比例更改显示图像的大小,因为原本的这个yolov5项目是按原图像显示的,按我的焦距拍出来的视频会撑爆整个屏幕,所以要缩小显示,这个函数会在150行显示的时候被调用;
# 改变显示图片大小(自定义函数)
    def cv_show(p, im0):
        height, width = im0.shape[:2]
        a = 1200 / width  # 宽为1200,计算比例
        size = (1200, int(height * a))

        img_resize = cv2.resize(im0, size, interpolation=cv2.INTER_AREA)
        cv2.imshow(p, img_resize)
        cv2.waitKey(1)  # 1 millisecond

(2)116行做一个判断,置信度低的就不要显示了,有时候地上一个图案都能被识别成人,用官方权重来检测,检测到的东西几乎都会被框上,这样会很杂,我们可以根据需求选择我们想框的东西,像我一样在118行和131行各做了一个判断,分别是判断是不是人和车,是的话就框出来并做相应处理,128行调用的plot_one_box()就是画框的函数;

if conf2 > 0.4:   # 置信度小于0.4时不显示
	# person,显示person标签的框,并单独做person的测距
	if names[int(cls)] == 'person':
plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3,
                                             name=names[int(cls)])     # 画框函数

(4)这个函数在utils里的polts.py里,我们来到polts.py里的59行,我给这个函数添加了一个参数name,在画框的时候函数会根据name是人还是车进行相应的测距函数调用(68行和70行,参数h是目标的高度,63~65行通过方框坐标计算得出),所调用的测距函数也就是前面我定义好的distance.py里边的。然后71行就是在原本的方框标签上追加我们测距得到的距离数据;

def plot_one_box(x, img, color=None, label=None, line_thickness=3, name=None):
c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))   # 传过来的x包含有框的两个对角坐标
# w = int(x[2]) - int(x[0])   # 框的宽
h = int(x[3]) - int(x[1])     # 框的高
dis_m = 1.00
    if name == 'person':    # 根据标签名称调用不同函数计算距离
        dis_m = detect_distance_person(h)
    elif name == 'car':
        dis_m = detect_distance_car(h)
    label += f'  {dis_m}m'   # 在标签后追加距离

(5)这点不是很重要,detect.py189行,是否保存检测结果到runs/detect目录下,我这里关闭了,省储存,这点根据需求来改,需要保存就把它改成store_true,然后在runs/detect目录下最后一个exp里找,因为每运行一次detect.py都会生成一个exp文件,最后一次运行结果自然保存在最后一个exp里;

# store_true为保存视频或者图片,路径为runs/detect
parser.add_argument('--nosave', action='store_false', help='do not save images/videos')   
  1. 总结就是,基于yolov5的检测,调用我们定义的测距函数,拿到检测距离之后,把距离追加到方框的标签后面。
2.5.1 实测自己的视频
  1. 自己用拍摄A4纸的方法,到路边横屏拍摄视频,相机到腰的位置,拍点人或者车,注意不能改变焦距来拍,用拍摄A4纸的状态来拍。要问为什么不能竖屏拍,我只能说,我竖屏拍了视频传到项目里运行,显示的图像会上下颠倒,不懂是什么原因;
  2. 拍好视频后,把视频传电脑里,复制到项目data/videos目录下,重命名一下,其实放哪里都无所谓,等会路径写对就好。
  3. 来到detect.py的181行,更改数据源,改成刚刚那个视频的路径。
  4. 见证奇迹的时刻到了,运行detect.py,看看效果。

3、后语

  小学弟我才学疏浅,非计算机科班出身,对编程感兴趣,自学Java出身,因为毕业设计意外选到人工智能的课题,所以临时自学Python,然后慢慢接触人工智能。
  本文章虽然通俗易懂,但是缺乏专业术语。
  代码中能看到的中文注释我建议简单理解一遍。
  若有不足,望大佬指教!

有关基于yolov5的目标检测和单目测距的更多相关文章

  1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  5. ruby - 检测由 RSpec、Ruby 运行的代码 - 2

    我想知道我的代码是否在rspec下运行。这可能吗?原因是我正在加载一些错误记录器,这些记录器在测试期间会被故意错误(expect{x}.toraise_error)弄得乱七八糟。我查看了我的ENV变量,没有(明显的)测试环境变量的迹象。 最佳答案 在spec_helper.rb的开头添加:ENV['RACK_ENV']='test'现在您可以在代码中检查RACK_ENV是否经过测试。 关于ruby-检测由RSpec、Ruby运行的代码,我们在StackOverflow上找到一个类似的问题

  6. ruby - 使用 Ruby Daemons gem 检测停止 - 2

    我正在使用rubydaemongem。想知道如何向停止操作添加一些额外的步骤?希望我能检测到停止被调用,并向其添加一些额外的代码。任何人都知道我如何才能做到这一点? 最佳答案 查看守护程序gem代码,它似乎没有用于此目的的明显扩展点。但是,我想知道(在守护进程中)您是否可以捕获守护进程在发生“停止”时发送的KILL/TERM信号...?trap("TERM")do#executeyourextracodehereend或者你可以安装一个at_exit钩子(Hook):-at_exitdo#executeyourextracodehe

  7. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  8. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

  9. ruby - Ruby 脚本如何检测到它正在 irb 中运行? - 2

    我有一个定义类的Ruby脚本。我希望脚本执行语句BoolParser.generate:file_base=>'bool_parser'仅当脚本作为可执行文件被调用时,而不是当它被irbrequire(或通过-r在命令行上传递)时。我可以用什么来包装上面的语句,以防止它在我的Ruby文件加载时执行? 最佳答案 条件$0==__FILE__...!/usr/bin/ruby1.8classBoolParserdefself.generate(args)p['BoolParser.generate',args]endendif$0==_

  10. ruby - 如何使用 Ruby 基于字母数字字符串生成颜色? - 2

    我想要像“嘿那里”这样的东西变成,例如,#316583。我希望将任意长度的字符串“归结”为十六进制颜色。我不知道从哪里开始。我在想,每个字符串的MD5散列都是不同的-但如何将该散列转换为十六进制颜色数字? 最佳答案 你可以只取几位前几位:require'digest/md5'color=Digest::MD5.hexdigest('Mytext')[0..5] 关于ruby-如何使用Ruby基于字母数字字符串生成颜色?,我们在StackOverflow上找到一个类似的问题:

随机推荐