2020电子设计竞赛G题 - 非接触物体尺寸形态测量_2020电赛g题非接触方案-程序员宅基地

技术标签: python  笔记  机器学习  计算机视觉  

非接触物体尺寸形态测量 - OpenMV

需求

需求

测试场景布置图

测试场景布置图

目标板和背景板放置示意图

目标板和背景板放置示意图

思路

​ 题目上有三种颜色三种形状的物体, 可以先判断物体的颜色二值化后判断物体形状, 这样可以有效降低误差, 但是后来测试的时候经常会发现有误判的现象, 想到可能是因为背景嘈杂并且二值化后的图像信息会有损耗, 我开始思考为什么人类识别这些物体的成功率如此之高, 自我认为是因为我们会聚焦到一个物体并且不断找出这个物体的特征然后根据经验判断出物体是什么, 因此我设计了一个几率模型,或者说是一种擂台法则, 把在一段时间内检测到的数据进行整合得到一个几率表, 根据擂台法则得出物体的准确信息

大概可分为以下几个要点:

  1. 聚焦
  2. 识别图形颜色以及形状
  3. 建立几率表
  4. 获取距离
  5. 根据物体的距离算出物体大小 (好像要用到线性拟合公式但是我当时并不知道有这么个东西, 自己根据大量数据来找规律最后侥幸得出了一个公式)
  6. 自动寻找目标
  7. 立体目标测量 (因为时间关系做出来的程序准确率不高, 在这里就不介绍了)

根据题目要求, 程序分为两种运行模式:

flag A:
      确认物体颜色,获得距离算出物体大小
flag B:
	先确认物体颜色, 然后再跟踪物体, 跟踪物体之后测量出距离, 随后进行尺寸计算

实现

聚焦

​ 所谓聚焦,就是让当前画面中只出现自己所关心的内容, 我使用OpenMV提供的图像二值化方法来实现这一点

在这里插入图片描述

根据像素是否在阈值列表 thresholds 中的阈值内,将图像中的所有像素设置为黑色或白色。

thresholds 必须是元组列表。 [(lo, hi), (lo, hi), ..., (lo, hi)] 定义你想追踪的颜色范围。 对于灰度图像,每个元组需要包含两个值 - 最小灰度值和最大灰度值。 仅考虑落在这些阈值之间的像素区域。 对于RGB565图像,每个元组需要有六个值(l_lo,l_hi,a_lo,a_hi,b_lo,b_hi) - 分别是LAB L,A和B通道的最小值和最大值。 为方便使用,此功能将自动修复交换的最小值和最大值。 此外,如果元组大于六个值,则忽略其余值。相反,如果元组太短,则假定其余阈值处于最大范围。

注解:

获取所跟踪对象的阈值,只需在IDE帧缓冲区中选择(单击并拖动)跟踪对象。 直方图会相应地更新到所在区域。然后只需写下颜色分布在每个直方图通道中起始与下降位置。 这些将是 thresholds 的低值和高值。 由于上下四分位数据相差微小,故手动确定阈值为佳。

您还可以通过进入OpenMV IDE中的 工具 ->机器视觉 ->阈值编辑器 并从GUI窗口中拖动滑块来确定颜色阈值。

invert 反转阈值操作,像素在已知颜色范围之外进行匹配,而非在已知颜色范围内。

设置 zero 为True来使阈值像素为零,并使不在阈值列表中的像素保持不变。

mask 是另一个用作绘图操作的像素级掩码的图像。掩码应该是一个只有黑色或白色像素的图像,并且应该与你正在绘制的 image 大小相同。 仅掩码中设置的像素被修改。

to_bitmap 将图像数据转换为二进制位图图像,每个像素以1位的形式存储。 对于非常小的图像,新的位图图像可能无法放入原始图像中,因此需要使用 copy 进行就地操作。

copy 如果为True,则在堆上创建二进制图像的副本,而不是修改源图像。

注解:

位图图像就像只有两个像素值(0和1)的灰度图像一样。 此外,位图图像被打包,这样它们每个像素仅存储1位,因此非常小。 OpenMV图像库允许位图图像在所有位置的 sensor.GRAYSCALEsensor.RGB565 中使用。 但是,将许多操作应用于位图图像时没有任何意义,因为位图图像只有2个值。 OpenMV建议在操作中对 mask 值使用位图图像,因为它们很容易适合MicroPython堆。 最后,将位图图像像素值0和1应用于 sensor.GRAYSCALEsensor.RGB565 图像时, 将被解释为黑白。该库自动处理转换。

返回图像对象,以便您可以使用 . 表示法调用另一个方法。

不支持压缩图像和bayer图像。

封装一个工具方法方便下面的程序编写:

# binary filter
def testBinary(img, thresholdValue):
    return img.binary([thresholdValue])

分别判断三种形状

圆形判断

使用MicroPython.img对象自带的Hough变换查找圆形

def isCircle(img):
    circles = img.find_circles(
        threshold=5000, x_margin=10, y_margin=10,
        r_margin=10, r_min=8, r_max=100, r_step=2)
    print("circles: ", circles)
    if (len(circles) < 1):
        return False
    return circles[0].r()  # 像素直径
三角形判断

三角形使用占空比判断, density函数可以自动返回色块面积/外接矩形面积 的值

def isTriangle(img):
    sideLength = 0  # 存放测量到的像素块的边长
    blobs = img.find_blobs([gray_threshold])
    # print(blobs)
    blob = find_max(blobs)
    if (blob == None):
        return False
    print('该形状占空比为', blob.density())
    # density函数可以自动返回 色块面积/外接矩形面积 的值
    if 0.56 > blob.density() > 0.50:
        print("检测为三角型  ", end='')
        # img.draw_cross(blob.cx(), blob.cy(), color=(255,0,0), size=5)
        print('三角型边长', blob.w())
        sideLength = blob.w()
        print("triangle sideLength: ", sideLength)
        return sideLength
    return False
矩形判断

矩形同样使用占空比判断, density函数可以自动返回色块面积/外接矩形面积 的值

def isRect(img):
    blobs = img.find_blobs([gray_threshold])
    print(blobs)
    blob = find_max(blobs)
    if (blob == None):
        return False
    # for blob in blobs:
    print('该形状占空比为', blob.density())
    if 0.99 > blob.density() > 0.87:
        print("检测为矩形  ", end='')
        print('矩形边长', blob.w())
        return blob.w()
    return False

物体的距离&大小

最终决定采用激光测距

关于尺寸计算公式实在想不起来当时怎么计算的了, 实验表明只能在200cm~300cm之间并且只能测量圆形的尺寸

使用像素计算

这种方式参考OpenMV官方例程, 倒也可以, 但是误差较大, 像素测距以及计算尺寸的部分代码:

'距离的K值 L=k/像素'
k = 2400
def isCircle(img):
    print(img)
    res = {
    'L': 0, 'R': 0}  # 距离 直径
    circles = img.find_circles(
        threshold=5000, x_margin=10, y_margin=10,
        r_margin=10, r_min=8, r_max=100, r_step=2)
    print("circles: ", circles)
    if (len(circles) < 1):
        return False
    for circle in circles:
        # img.draw_circle(circle.x(), circle.y(), circle.r(), color=(255, 0, 0))
        L = k / circle.r()
        res['L'] = L  # 距离
        k1 = (L - 200) * 0.04
        R = (k1 + circle.r()) * 3.04
        res['R'] = R  # 直径
    print("isCircle:res: ", res)
    return res
使用激光测距模块计算

采用这种方式好处就是激光测距本身就带有激光灯, 并且极其准确, 缺点就是需要占用串口

PLS-K-60 激光测距模块:
PLS-K-60 激光测距模块

接线图:
在这里插入图片描述

用串口给激光测距发送特定字节命令可以操作它, 部分指令:

详细数据手册稍后上传至CSDN资源: PLS-K-60激光测距模块产品手册.pdf-CSDN下载
指令说明

激光测距程序实现:

def getDistance():
    # uart.write(b'D')  # 测距离
    while(True):  # 因为我需要调用一次这个方法就返回给我准确的数据, 所以这里需要一直等待着获取到有用的数据
        uart.write(b'D')  # 测距离 0x44
        v = uart.read(1024)
        # print(v)
        if(v != None):
            # print(v)
            s = v.decode('utf-8')
            if(len(s) == 15):
                s = s[1:7]
                return float(s.strip())*100  # 将距离(cm)反馈出去
        else:
            continue


# 获得距离&尺寸   Distance & Size
# 根据物体的距离算出物体大小
def getDS(res):
    ds = {
    'd': 0, 's': 0}
    L = getDistance()
    k1 = (L - 200) * 0.04
    S = (k1 + int(res[2])) * 3.04  # size
    ds['d'] = L
    ds['s'] = S
    return ds

建立几率模型

​ 最最最重要的一步就在这里, 首先从threshold中取出一种颜色对图像进行二值化处理, 然后用二值化后的图像去判断是什么形状, 将会对每个颜色的二值化图片检测5遍, 将每次检测到的图形和颜色都暂存到一个py字典中, 来增加可能是这种物体的几率. 这样做的好处就是既可以获取到当前物体的颜色形状顺便还可以将检测到的物体的边长记录下来.

def areba(img):
    # a=形状 c=color s=shape
    a = {
    'c': 0, 'r': 0, 't': 0}  # circle rect triangle
    c = {
    '0': 0, '1': 0, '2': 0}  # red grenn blue
    s = {
    } # pix_size
    count = 0
    # 形状字典擂台,规定次数内谁的命中次数最多就获胜
    for i in range(3):  # 三种颜色
        img = sensor.snapshot()
        print(threshold[i])
        img = testBinary(img, threshold[i])
        for x in range(5):  # 五次确认形状
            count = count + 1
            print("count: ", count)
            # 纠结到底要不要再次拍照
            img = sensor.snapshot()
            img = testBinary(img, threshold[i])
            # 暂存结果
            cir = isCircle(img)
            rect = isRect(img)
            tria = isTriangle(img)
            if (cir != False):
                a['c'] = a['c'] + 1  # 增加形状几率
                c[str(i)] = c[str(i)] + 1  # 增加颜色几率
                if (s.get(str(cir)) != None):  # 已经包含这个Key
                    s[str(cir)] = s[str(cir)] + 1  # 增加像素尺寸机率 k=数据,v=命中次数
                else:  # 不包含就创建
                    s[str(cir)] = 0
                s[str(cir)] = s[str(cir)]+1
            if (rect != False):  # -------- 矩形开始 ---------
                a['r'] = a['r'] + 1
                c[str(i)] = c[str(i)] + 1
                if (s.get(str(rect)) != None):  # 已经包含这个Key
                    s[str(rect)] = s[str(rect)] + 1  # 增加像素尺寸机率 k=数据,v=命中次数
                else:  # 不包含就创建
                    s[str(rect)] = 0
            if (tria != False):  # -------- 三角形开始 ---------
                a['t'] = a['t'] + 1
                c[str(i)] = c[str(i)] + 1
                if (s.get(str(tria)) != None):  # 已经包含这个Key
                    s[str(tria)] = s[str(tria)] + 1  # 增加像素尺寸机率 k=数据,v=命中次数
                else:  # 不包含就创建
                    s[str(tria)] = 0

    # print("颜色: ", c)
    # print("形状: ", a)
    # dict(a, **c)
    return [a, c, s]  # 合并字典 https://www.cnblogs.com/lmh001/p/9888156.html
处理结果集

处理结果集, 判断谁获胜 将获胜方的数据返回出去

重要思想: 颜色形状擂台可以和尺寸距离擂台分别对战,最后两方面的获胜者一定说的是同一个物体

def handle_data(data):
    shapes = {
    'r': 'Rect', 'c': 'Circle', 't': 'Tirangle'}
    colors = ['Red', 'Green', 'Blue']
    shape = ''
    color = ''
    size = ''
    max_value = 0
    for (k, v) in data[0].items():
        if (v > max_value):  # update max_value
            max_value = v
            shape = shapes[k]
    max_value = 0
    for (k, v) in data[1].items():
        if (v > max_value):
            max_value = v
            color = colors[int(k)]
    max_value = 0
    for (k, v) in data[2].items():
        if (v > max_value):
            max_value = v
            size = k
    return [shape, color, size]

完整代码

查看我的Gitee:

GFinal.py · Missper/Race_G - 码云 - 开源中国 (gitee.com)

一些参考资料

后续可能会使用机器学习,或者小型神经网络来改善这个项目

G题_辽宁赛区_大连理工大学——非接触物体尺寸形态测量 - 2020年TI杯省赛作品交流区 - 全国大学生电子设计竞赛培训网 (nuedc-training.com.cn)

Binder (mybinder.org)

Jupyter Notebook介绍、安装及使用教程 - 简书 (jianshu.com)

关于TensorFlow | TensorFlow中文官网 (google.cn)

OpenMV Cam H7 Plus (edgeimpulse.com)

让数以百万计的Arduino开发者可以在微控制器中可以轻松使用机器学习,只因为有了它 - 知乎 (zhihu.com)

mask-tflite/README_CN.md at main · SingTown/mask-tflite (github.com)

关于TensorFlow | TensorFlow中文官网 (google.cn)

OpenMV Cam H7 Plus (edgeimpulse.com)

让数以百万计的Arduino开发者可以在微控制器中可以轻松使用机器学习,只因为有了它 - 知乎 (zhihu.com)

mask-tflite/README_CN.md at main · SingTown/mask-tflite (github.com)

口罩检测 · OpenMV中文入门教程

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45790919/article/details/119187870

智能推荐

Python爬虫库学习笔记-BeautifulSoup-程序员宅基地

文章浏览阅读425次。BeautifulSoup学习笔记

DefaultSerializer requires a Serializable payload but received an object of type_march of Time的博客-程序员宅基地

文章浏览阅读1.1k次。解决方式:进行序列化在实体类中加上public class User implements Serializable { private static final long serialVersionUID = -2867071904568397169L;}再次启动:可以看到返回了正确的查询结果。

使用STM32测量PWM方波频率和占空比的方法_stm32方波频率-程序员宅基地

文章浏览阅读1w次,点赞4次,收藏69次。简介使用STM32中TIMER的输入捕获功能可以测量PWM方波的频率和占空比。测量通常有两种方法:中断法和DMA传输法。根据我的经验,中断法最多能测量到150KHz的方波,而DMA方法最多可达1MHz(注:在这种极限状态下,信号占空比太大也容易测量失败)。下面就分别介绍这两种方法。1. 使用STM32CubeMX生成代码首先是选用TIMER,TIMER必须要选用两个相邻通道,并且一个是Input Capture Direct Mode,另一个是Input Capture Indirect Mode。在_stm32方波频率

使用阿里云搭建网站并实现站库分离_什么是站库分离-程序员宅基地

文章浏览阅读1k次。使用阿里云搭建自己的网站并用阿里云RDS实现站库分离,本文章将从注册域名到部署网站详细地讲解。原文:https://blog.zeruns.tech/archives/513.html什么是站库分离:站库分离就是网站和数据库不在同一个服务器上,数据库用的是内网网络;这样的操作模式更快,更安全;很多大型的企业都采用站库分离的模式。推荐几个网站程序,自己根据需要选择:论坛:DiscuzX博客..._什么是站库分离

代码函数应该怎么命名才方便呢?如何优雅地为程序中的变量和函数命名?_写代码的时候函数或者变量怎么命名-程序员宅基地

文章浏览阅读641次。觉得写得不错,是个方法,转到自己的博客里留着看作者:何新宇链接:https://www.zhihu.com/question/21440067/answer/24522844来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 不同的代码段采用不同的命名长度。通常来说,循环计数器(loop counters)采用1位的单字符来命名,循环判断变量(condi..._写代码的时候函数或者变量怎么命名

uniapp 中tab切换多个单页面跳转v-if 和 click点击事件配合web页面也适用_uni app tablist 多页面_MFG_666的博客-程序员宅基地

文章浏览阅读1.1k次。<template> <view class="page"> <view class="content flex" > <!-- D --> <view class="left" > <view class="left_title" @click="linkA()"> A标题 </view> <view class="left_txt" v-if="type_uni app tablist 多页面

随便推点

org.apache.shiro.cache.CacheException: net.sf.ehcache.CacheException: java.io.StreamCorruptedExcepti-程序员宅基地

文章浏览阅读1w次,点赞8次,收藏8次。昨天电脑忘关机了,今天登陆本地项目就报这错org.apache.shiro.cache.CacheException: net.sf.ehcache.CacheException: java.io.StreamCorruptedException: invalid stream header: 1B000000大概意思shiro验证ehcache出问题了 ,我清掉 t..._org.apache.shiro.cache.cacheexception: net.sf.ehcache.cacheexception: java.i

IEEE Access投稿经验分享,多久能给意见,多久能检索._ieee access审稿周期-程序员宅基地

文章浏览阅读1.5w次,点赞15次,收藏57次。首先要申明,本人双非研究生,对于我的水平来说。IEEE Access已经很不错了。如果有什么清华北大中科院的大神请绕道。研一的一年中趁着上课的空闲基本弄完了课题。算法结构或者说网络的完成是在2020年疫情7月份的时候。然后就穿插着写论文做实验。之后老师又提出意见,修改然后继续做实验。一直到2021年2月23号才投稿,大半年的时间主要是因为导师在华旧金山大学访学了一年,沟通交流主要靠腾讯会议,日夜颠倒也不是很方便,而且我们一个老师研一研二研三同时带了8个学生,好在我比较积极,一直在催老师。如果你只_ieee access审稿周期

android自定义带箭头半圆进度,Android自定义半圆进度条 半圆渐变色进度条带指示 半圆开口大小可自由修改...-程序员宅基地

文章浏览阅读348次。Android自定义半圆进度条 半圆渐变色进度条带指示 半圆开口大小可自由修改首先我们来看下效果图不同的开口大小只需要修改一个参数即可半圆1:半圆2:半圆3:如果是你想要的效果,就直接滑动到文章底部,下载源码,然后结合本文章操作说明集成到自己的项目中吧如果集成第一步:下载项目解压,然后用as打开。第二步:复制SemicircleProgressBar这个类到自己的项目中第三步:在需要显示的布局中,..._android 半圆进度条

(3)Spring Boot 配置profile多环境支持_sping 3 profile支持-程序员宅基地

文章浏览阅读200次。Spring Boot 配置profile多环境支持**前言:**在我们进行项目部署的时候,肯定很多配咋和开发环境是有很多不同的,那么我们第一种方法就是去修改application.properties文件,但是这显然太麻烦了,所以springboot也是支持多环境配置的。1.多profile文件我们在主配置文件编写的时候,文件名可以是application-{proffile}.prope..._sping 3 profile支持

Win7提高效率工具软件--AutoHotKey_autokeypress-程序员宅基地

文章浏览阅读1.7k次,点赞2次,收藏4次。参考:https://blog.csdn.net/liuyukuan/article/details/77888169这是一款免费的、Windows平台下开放源代码的热键脚本语言。一、安装使用步骤1:官网下载https://www.autohotkey.com/打开后:直接下载。步骤2:安装。没有特别之处,一路安装即可。步骤3:notepad++或者ed..._autokeypress

js 动态修改iframe的src_js设置iframe的src-程序员宅基地

文章浏览阅读2.1w次,点赞5次,收藏12次。项目组里有功能需要,当前页面获取url传递过来的参数值,动态赋值给iframe的src,从而实现动态加载iframe的需求,代码如下:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html> <head> <title>打印</title> <meta http-equiv="Content-Type" content="text/html; cha_js设置iframe的src

推荐文章

热门文章

相关标签