花名/ID: 天析

邮箱: 2200475850@qq.com

主攻方向: 物联网安全、WEB安全

兴趣爱好: 乒乓球、羽毛球、排球、业余无线电···

备案信息:蜀ICP备19023334号

机器视觉 - 高空坠物捕捉的实现

0x01: 前言

嘤嘤嘤,不好意思,标题党了~
本来这个月想发一篇关于物联网相关的文章的,但是现在发现到了这个月底依旧还没写完,看样子只能等到省赛结束之后在发了,苦于无文章发,只好先把这一篇水文放出来了,由于这个课题是我所在的团队(校内的团队)参与某比赛的课题(比赛还没结束,希望我这篇文章他们刷不到,刷到了打死也不承认是我写的),我只能给出一个前期我的设想和我的思路(现版本的代码越来越复杂300多行快400行了,我本来极力想开源出来,奈何团队负责人说开源就嫩死我,开发期间我就发了个效果图的说说,都被他骂了,哼,男人!),这篇文章里的代码(网上随便找的段代码改了改就成我的了,原作者勿怪,我不是有意的~)已经能够实现对于运动的物体进行捕捉跟踪,已经基本实现我们这个项目的核心功能(准确识别坠物)了。

后面的时间,我争取一个月发一篇略有技术含量的文章,熟悉我的师傅们应该都知道我转身到物联网安全方向去了,学习物联网相关的知识也有将近一年了,但我却没有找到物联网安全这里的安全在何处体现,学习了一大堆的MQTT、DNP3、Modbus之类的通信协议和一堆硬件设备,比如Zigbee,lora之类的,和老师一起做了一些物联网工程建设的相关项目,近些年CTF中也开始出现关于物联网,或者说工控相关的题目了,但是看着一些外表上是工控的题目,我更是迷茫了,有没有师傅同混物联网的给点建议喃~

导语

现如今高空抛物可能会造成人身伤亡和重大财产损失的严重后果。高空抛物现象也被称为 “悬在城市上空的痛”,在某些场景下,它比乱扔垃圾更恶劣。

硬件环境 & 软件环境 搭建部署

工欲善其事,必先利其器,为了携带足够方便,我选择了一个树莓派4B作为主机,接了一个300万像素UVC协议的摄像头。机器视觉这个东西,真正的大佬才能玩转起来的,我作为一个渣专,肯定玩不到各位大佬的那个层次(比如MATLAB搞的动态跟踪,当初我在刚开始找相关资料的时候,看到大佬们用MATLAB写,我人都傻了),因此我选择了使用Python来肝。

background = None  # 背景置空
camera = cv2.VideoCapture(video)

OpenCV,这个不用说,肯定是不能少。对于怎么安装python的opencv-python库,这个我觉得我没必要再写出来,就不辣各位师傅们的眼睛了。

给大家看一下我的这篇文章用到的所有设备吧

左树莓派,右摄像头
左树莓派,右摄像头

实现原理叙述

要想实现对高空坠物的跟踪,我们首先要做到的是做到跟踪运动的物体,而要做到跟踪运动的物体,我们首先要做到运动的物体的检测。

运动检测

运动检测分为两种,一种是静态检测,即背景相对静止,或者说固定的检测,比如说墙面,只有检测的目标在运动,另一种是动态背景的检测,比如流动的河流上检测运动的物体。
从检测难度上来看,我个人觉得动态检测的难度比静态检测的难度会更高或者高很多,好在我们识别高空坠物属于静态背景的检测,相对更简单一些。
    

灰度处理

_, RGB_IMG = camera.read()
GRAY_IMG = cv2.cvtColor(RGB_IMG, cv2.COLOR_BGR2GRAY) 

 因为我们太菜了,处理不来带色彩的图像,嘤嘤嘤~
   
    各位师傅们都给自己的女朋友买过口红吧,相信你们对颜色也有了一定的研究。
    
色彩深度

以下这段内容来源于维基百科:

色彩深度简称色深,在计算机图形学领域表示在位图或者视频帧缓冲区中储存每一像素的颜色所用的位数,常用单位为位/像素(bpp)。色彩深度越高,可用的颜色就越多。

色彩深度是用“n位颜色”(n-bit colour)来说明的。若色彩深度是n位,即有2n种颜色选择,而储存每像素所用的位数就是n。常见的有:

  • 1位:2种颜色,单色光,黑白二色,用于compact Macintoshes。
  • 2位:4种颜色,CGA,用于gray-scale早期的NeXTstation及color Macintoshes。
  • 3位:8种颜色,用于大部分早期的电脑显示器。
  • 4位:16种颜色,用于EGA及不常见及在更高的分辨率的VGA标准,color Macintoshes。
  • 5位:32种颜色,用于Original Amiga chipset。
  • 6位:64种颜色,用于Original Amiga chipset。
  • 7位:128种颜色。
  • 8位:256种颜色,用于最早期的彩色Unix工作站,低分辨率的VGA,Super VGA,AGA,color Macintoshes。其中红色和绿色各占3位,蓝色占2位。
  • 灰阶:有256种灰色(包括黑白)。若以24位模式来表示,则RGB的数值均一样,例如(200,200,200)。
  • 9位:512种颜色
  • 10位:1024种颜色,
  • 12位:用于部分硅谷图形系统,Neo Geo,彩色NeXTstation及Amiga系统于HAM mode。
  • 16位:用于部分color Macintoshes(红色占5个位、蓝色占5个位、绿色占6个位,所以红色、蓝色、绿色各有32、32、64种明暗度的变化总共可以组合出64K种颜色)。
  • 24位:有16,777,216色,真彩色,能提供比肉眼能识别更多的颜色,用于显示照片。 彩色图像,就是常说的24位真彩,约为1670万色。
  • 32位:基于24位而生,增加8个位的透明通道。

另外有高动态范围影像(High Dynamic Range Image),这种影像使用超过一般的32位色阶来储存影像,通常来说每个像素会分配到32+32+32个bit来储存颜色资讯,也就是说对于每一个原色都使用一个32bit的浮点数来储存.
    
    色彩梯度
    
    以下这段内容来源于维基百科:
    
色彩梯度(有时也叫颜色带或颜色渐变)是指在计算机图形设计中,指定了一定范围的相关颜色,通常用于填充某一个区域。
    
我们简单的了解到了色彩深度和色彩梯度后,可以发现,我们去识别一个物体,第一个关键的因素就是色彩的梯度,接着是色彩的深度,但是这两者是关联起来的,彩色的图片信息量很多时候对于机器来说,有些过大,做个简单的对比,24位真彩色,一个像素点,就有约为1670万种颜色,也就是1670万个梯度(或者说维度),而灰阶的一个点,最多也就256种灰色,我们把图片做灰度处理过后,虽然失去了颜色信息,但是这个图片的梯度被增强了,更有利于我们对于目标物体的识别,同时降维后矩阵的维数下降,运算的难度得到一个极大的下降,因为难度的下降,运算的速度也得到了一个极大的提升。

纵观市面上一些主流的特征提取算法,究其本质都是围绕着梯度展开的。

图像降噪

GRAY_IMG = cv2.GaussianBlur(GRAY_IMG, (15, 15), 0)

对于图像来说,我个人觉得降噪是必不可少的,主流的一些图像滤波手段无非是均值、中值,双边和高斯滤波,刚开始写的时候,并没有考虑降噪这一点,绘制的矩形框总是框总是不能很好的一直跟踪下去,对比了几个降噪手段之后,我最后还是选择了高斯模糊。

识别掉落的雨滴(未降噪)
识别掉落的雨滴(未降噪)

高斯滤波
高斯滤波

均值滤波
均值滤波

双边滤波
双边滤波

背景差分

if background is None:
    background = GRAY_IMG
    continue

DIFF_IMG = cv2.absdiff(background, GRAY_IMG)

为了简单快速的把目标物体找出来,我在这里选择了比较简单的背景差分法(在后面的版本中,为了保留物体的轨迹图像使我放弃了背景差分法而选择了相邻帧),背景差分法是一种在静态背景下寻找运动的物体的通用方法,它将当前获取的图像帧与背景图像做差分运算,得到目标运动区域的灰度图。

二值化处理

DIFF_IMG = cv2.threshold(DIFF_IMG, 25, 255, cv2.THRESH_BINARY)[1]  

为了将目标彻底的和背景区分开来,我们需要对整个图像做一个二值化处理,只留下黑白两种颜色,这里也是为了方便后面findContours函数的使用。

形态学膨胀

DIFF_IMG = cv2.dilate(DIFF_IMG, cv2.getStructuringElement(cv2.MORPH_RECT, (9, 4)), iterations=2)

矩形框完全的框住目标这并不是我想要的,我们得把这个框变得稍微更大一点,才能恰到好处的框住这个图像。

形态学膨胀
形态学膨胀

取出目标物体轮廓

contours, hierarchy = cv2.findContours(DIFF_IMG.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 
for c in contours:
    (x, y, w, h) = cv2.boundingRect(c) 
    cv2.rectangle(RGB_IMG, (x, y), (x + w, y + h), (62, 255, 199), 2)  

膨胀完我们就得去取出物体的轮廓了,使用findContours函数去取轮廓即可。

优化识别范围

# 轮廓大于控制阀值时不绘制外框
if cv2.contourArea(c) > 1000:
    continue

为了提高一个识别的效率,我们需要对掉落的物体设置一个阀值,超过这个阀值的物体不进行捕捉,这么做的原因是我们之前测试的时候,摄像头一个意外的轻微的抖动捕获到了整个画面后不在捕获其他的东西了。
    

实现效果图片

到这里,基本的一个动态检测就算是完成了,给大家看捕捉的效果吧~
其实后面还有对于楼层溯源,物体运动轨迹标记等一系列的东西,

教学楼7楼扔沙包模拟高空坠物(仰角摄像头)
教学楼7楼扔沙包模拟高空坠物(仰角摄像头)

捕获到运动中的飞机
捕获到运动中的飞机

样例代码

一个坠物的识别我都写了这莫多行,太菜了太菜了。
新的版本其实就是在网上到处流传的代码上改了改,真让我玩算法还不一定玩得动~
给大家放出来个最基本的代码吧,基于网上的脚本改的,改改就是我的了,嘿嘿嘿~

# -*- coding:utf-8 -*-
"""
站在巨人的肩膀上,我们能走的更远!

    @Author:Tianxi <2200475850@qq.com>

    当前版本更新说明:
        - V0.0.1: 具备静态背景下的运动物体跟踪能力(第二次修正)
"""
import time
import cv2

# 开始草率的识别
def StartHADT(video):

    camera = cv2.VideoCapture(video)

    background = None  # 背景置空

    # 循环检测每一帧
    while True:
        _, RGB_IMG = camera.read()
        GRAY_IMG = cv2.cvtColor(RGB_IMG, cv2.COLOR_BGR2GRAY)  # 灰度处理

        # 用高斯滤波进行模糊处理
        GRAY_IMG = cv2.GaussianBlur(GRAY_IMG, (15, 15), 0)

        # 配置背景
        if background is None:
            background = GRAY_IMG
            continue

        DIFF_IMG = cv2.absdiff(background, GRAY_IMG) # 差分处理
        DIFF_IMG = cv2.threshold(DIFF_IMG, 25, 255, cv2.THRESH_BINARY)[1]  # 2值化黑白处理
        DIFF_IMG = cv2.dilate(DIFF_IMG, cv2.getStructuringElement(cv2.MORPH_RECT, (6, 6)), iterations=2)  # 膨胀一下
        contours, hierarchy = cv2.findContours(DIFF_IMG.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 对比特征取出轮廓

        for c in contours:

            # 读取坐标
            (x, y, w, h) = cv2.boundingRect(c)

            # 轮廓大于控制阀值时不绘制外框
            if cv2.contourArea(c) > 4000:
                continue

            cv2.putText(RGB_IMG, str(time.strftime('%H:%M:%S')), (x + w + 2, y + h), cv2.FONT_HERSHEY_COMPLEX, 0.5, (125, 184, 222), 1, 4)
            cv2.rectangle(RGB_IMG, (x, y), (x + w, y + h), (62, 255, 199), 2)

        # 版权水印
        text = 'HADT Author Tianxi (' + str(time.strftime('%Y-%m-%d %H:%M:%S')) + ')'
        fontFace = cv2.FONT_HERSHEY_COMPLEX
        fontScale = 1
        fontcolor = (255, 255, 255)  # BGR
        thickness = 4
        lineType = 7
        cv2.putText(RGB_IMG, text, (30, 40), fontFace, fontScale, fontcolor, thickness, lineType)

        cv2.imshow('测试', RGB_IMG)  # openCV 拉起显示

        # 按下ESC键退出
        k = cv2.waitKey(1)
        if k & k == 27:
            break


# 入口方法
if __name__ == '__main__':
    StartHADT("q.mov")

总结 && 自嘲

其实在我这篇文章发布时,校赛阶段已经结束了,我们还在继续等待看能否入围省赛阶段,很遗憾的是我们这个项目的一些闪光点并没有被场下的评委发现,或者是因为我们是专科的原因,下面的评委们认为我们玩不起机器视觉吧,校赛的排名很是难堪,甚至都未比过某些PPT工程,现实总是与我的理想越来越背道而驰,越发的学术与现实其实处于两个极端,当初志同道合的一些人也渐行渐远······

文章所属分类:  物联网 

« 【2021.03.15】近段时间的一个自我总结 自动识别技术笔记 »