基于AidLux平台实现智慧社区中高空抛物和车辆车牌识别

本文针对智慧社区里面的两个典型场景:高空抛物和车牌识别,记录评测了基于AidLux平台的算法开发以及边缘部署的过程。

1智慧社区的背景

1.1智慧社区的各类场景算法的划分

智慧社区主要分成以下三个方面:住房安全管控、社区环境管控以及物业服务管控三个部分。住房安全管控,主要包括消防安全的监管和入侵安全之类。社区环境管控,主要是对小区内的公共环境实时监测。物业服务管控,主要是包括提升物业效率的智能化管控。

每个场景都对应着不同的算法业务功能,每个算法业务功能则是由不同的算法框架如:目标检测、图像分类、人脸检索等组合而成。

1.2智慧社区项目落地方式

一般的项目算法开发流程:

产品的开发流程,相对于项目的开发流程在整体上差不多,产品需求的明确性相对与项目稍差,同时涉及到投入市场后的用户反应等因素,所以需求变更稍频繁,同时考虑后期的拓展性等问题,在算法方案和算法接口上会考虑更多

算法项目,会有验收指标,但是如果有的客户对算法了解不多,需要算法参与指导制定验收指标。算法的评价分为两种:一种是算法模型的评价,另外一种是项目业务的评价

1.3边缘设备在智慧社区中的应用场景

现在外界很流行端边云协同系统,即指把简单的、需要实时计算和分析的过程放到离终端设备更近的地方,以保证数据数据处理的实时性,同时也减少数据传输的风险。

整体上说是边缘负责局部的数据分析和推理,而云端则汇集所有边缘的感知数据、业务数据以及互联网数据,完成对行业以及跨行业的态势感知和分析,而端指的是终端设备,如手机、智能家电、摄像头等。

2高空抛物识别

2.1背景简介

高空抛物是智慧社区的重要部分之一,主要为主动识别高空中抛下的物体,一般场景为以监看和事后取证为主。

我们在很多小区经常会看到类似的高空抛物相机,以仰视的角度,往住宅楼的角度拍摄,当发生抛物事件的时候,可以实时的监测到,当发生危险事故时,可以实时的去追踪,查看当时高空抛物的视频,追踪到底是从哪家的窗口抛出的。

2.2方案设计

难点在于以下几点:

(1)抛出的物体相对于整个楼栋的目标太小;

(2)干扰因素较多,如白天的飞鸟、飘落的树叶、夜晚的背景楼栋灯光等;

(3)环境影响如雨天、雾天、逆光等环境对结果影响较大。

2.2.1数据采集

难点在于相机的位置和角度的安装。对于不同的层高,建议的安装距离和分辨率选择对应的相机焦距不同。针对夜间低照度,小区夜间光照不足,不建议通过补光的方式来提高环境照度,这样的环境下可以选型超星光摄像头,最低支持0.0002lux。白天高空抛物摄像头对着天空,白天有强烈的太阳光会造成逆光现象,可以选型摄像头支持120db的宽动态摄像头,同时也支持背光补偿。

2.2.2算法设计

1. 使用传统的动态目标检测,如光流检测和帧差法;

2. 使用目标检测+目标追踪算法,对抛出的物体先做目标检测,并对检测到的物体做追踪;

3. 使用物体追踪+过滤算法;

4.  使用视频分类的算法。

对于第一种方法传统方法的动态目标检测,如光流检测和帧差法,稳定性稍差,优点在于对于数据要求低。

对于第二种方法,使用目标检测检测被抛物体,并通过目标追踪对抛出物体的运动轨迹做追踪,会受到背景的影响很大,因为楼宇间的灯光等,同时使用目标检测+目标追踪的方法,其难点在于小目标的检测,很容易出现漏检

对于第三种方法,针对第二种方法中的目标检测算法的效果不佳,采用高斯背景建模的方法,过滤背景信息。再使用目标追踪如kalman滤波,完成运动轨迹的记录,同时针对第二种方法中视频中会出现的树叶、飞鸟以及晒衣服等的摆动等不符合抛物运动的轨迹的误检,通过SOM网络进行聚类,SOM(自组织映射神经网络)会对不同运动的轨迹进行分析:排除掉不符合抛物运动的轨迹

对于第四种方法,使用视频分类的方式,即对一段时间内的视频流做视频分类,这里可以通过第三步中先通过视频抽帧完成高斯背景建模,过滤掉背景后,再对前处理后的视频完成视频的分类。

本文主要选择第一种方法。

2.3算法实现

流程是先在PC端完成算法的实现后,再移植到安卓端。

2.3.1去抖动

背景建模的前提是保证摄像机拍摄位置不变,保证背景是基本不发生变化的。

其主要的原理是通过每张图的特征,找到两个图片的关键点,并基于关键点获得变换矩阵后,将原图通过变换矩阵变换后,与第一张原始图片对齐。

   def debouncing(self, image, index, ratio=0.7, reprojThresh=4.0, showMatches=False):
        image = cv2.resize(image, (int(image.shape[1]/1), int(image.shape[0]/1))) # 截帧图片的长宽的量化
        start = time.time() 
        (kps, features) = self.detectAndDescribe(image) # 对图片灰度化,并基于ORB算法,定位到关键点
        print(f"take {time.time() - start} s") 
        M = self.matchKeypoints(kps, self.kps, features, self.features, ratio, reprojThresh) # 基于每一帧与背景帧,完成关键点的匹配,输出匹配变换矩阵、

        if M is None:
            return None
        (matches, H, status) = M

        """
        将图像按照变换映射M执行后返回变换后的图像result。
        参数: 
        * src input image.
        * dst output image that has the size dsize and the same type as src .
        * M  $ 3\cdot 3 $ transformation matrix.
        * dsize size of the output image.
        * flags  combination of interpolation methods (INTER_LINEAR or INTER_NEAREST) and the optional flag WARP_INVERSE_MAP, that sets M as the inverse transformation ( $\text{dst}\to \text{src}$ ).
        * borderMode  pixel extrapolation method (BORDER_CONSTANT or BORDER_REPLICATE).
        * borderValue  value used in case of a constant border; by default, it equals 0.
        """
        result = cv2.warpPerspective(image, H, (image.shape[1] + image.shape[1], image.shape[0] + image.shape[0])) 
        
        # 填充图片 opencv显示结果
        result = result[int(self.edge[1]):int(image.shape[0] - self.edge[1]),
                 int(self.edge[0]):int(image.shape[1] - self.edge[0])]

        start_img = self.start_image[int(self.edge[1]):int(image.shape[0] - self.edge[1]),
                    int(self.edge[0]):int(image.shape[1] - self.edge[0])]

        # 获取两张图的差分图
        sub_img = cv2.absdiff(result, start_img)
 
        return result

2.3.2背景建模

背景建模主要是为了检测运动物体,输出前景图片。

class knnDetector:
    def __init__(self, history, dist2Threshold, minArea):
        self.minArea = minArea 
        """
        此算法结合了静态背景图像估计和每个像素的贝叶斯分割。这是 2012 年Andrew_B.Godbehere,Akihiro_Matsukawa 和 Ken_Goldberg 在文章中提出的。它使用前面很少的图像(默认为前 120 帧)进行背景建模。使用了概率前景估计算法(使用贝叶斯估计鉴定前景)。这是一种自适应的估计,新观察到的对象比旧的对象具有更高的权重,从而对光照变化产生适应。一些形态学操作如开运算闭运算等被用来除去不需要的噪音。在前几帧图像中你会得到一个黑色窗口。对结果进行形态学开运算对与去除噪声帮助
        背景重建方法:MOG2 /knn 
        """
        self.detector = cv2.createBackgroundSubtractorKNN(history, dist2Threshold, False) # 背景建模

        """
        # 得到一个结构元素(卷积核)。主要用于后续的腐蚀、膨胀、开、闭等运算。
          因为这些运算都是依赖于卷积核的,不同的卷积核(形状、大小)对图形的腐蚀、膨胀操作效果不一样

        输入参数:
 		a设定卷积核的形状、b设定卷积核的大小、c表示描点的位置,一般 c = 1, 表示描点位于中心。
        返回值:
 		返回指定形状和尺寸的结构元素(一般是返回一个矩形)、也就是腐蚀/膨胀用的核的大小。
        """
        self.kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 

    def detectOneFrame(self, frame,index):
        if frame is None:
            return None
        start = time.time()
        mask = self.detector.apply(frame) # 背景重建,提取前景 
        # if index% 10 == 0 :
        #    cv2.imwrite(os.path.join(r"C:\Users\shime\Desktop\highthrow(1)\images", "mask_unprocess_{index}.jpg".format(index=index)), mask)
        stop = time.time()
        print("detect cast {} ms".format(stop - start))
        # cv2.namedWindow("mask_unprocess", cv2.WINDOW_NORMAL)
        # cv2.imshow("mask_unprocess", mask)

        start = time.time()
        mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, self.kernel) # 做开运算 先腐蚀,再膨胀
        # if index% 10 == 0 :
        #    cv2.imwrite(os.path.join(r"C:\Users\shime\Desktop\highthrow(1)\images", "mask_process_open_{index}.jpg".format(index=index)), mask)
        mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, self.kernel) # 再膨胀
        # if index% 10 == 0 :
        #    cv2.imwrite(os.path.join(r"C:\Users\shime\Desktop\highthrow(1)\images", "mask_process_dilate_{index}.jpg".format(index=index)), mask)
        stop = time.time()
        print("open contours cast {} ms".format(stop - start))

        start = time.time()
        contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 基于mask提取轮廓
        stop = time.time()
        print("find contours cast {} ms".format(stop - start))
        i = 0
        bboxs = []
        start = time.time()
        for c in contours:
            i += 1
            if cv2.contourArea(c) < self.minArea: # 过滤
                continue

            bboxs.append(cv2.boundingRect(c)) # 基于轮廓 寻找外接矩形 
        stop = time.time()
        print("select cast {} ms".format(stop - start))

        return mask, bboxs

2.3.3形态学处理

前景的mask 中存在很多的干扰,如灯光的干扰等,再通过形态学处理将干扰项移除。

2.3.4目标检测

在第三步过滤掉干扰过后,找到目标的外接轮廓,同时过滤掉小的斑点干扰后,提取目标的外接矩形。

2.3.5目标追踪

SORT是 SIMPLE ONLINE AND REALTIME TRACKING 的简写,并不是什么排序算法。

其核心算法是匈牙利算法+卡尔曼滤波。

SORT算法没有用到特征跟踪,其本质实际上是根据观测的位置预测下一帧出现的位置,而我们预测的高空抛物实际上是有很强的规律的(重物规律强,较轻的物体如塑料袋或者纸板等,不是很规律,但是其速度不快,在每一帧之间基本上都有IOU重叠,因此也不会漏检),所以完全可以用此算法。

2.4基于Aidlux平台的部署和推理测试

2.4.1平台简介

Idlux主打的是基于ARM架构的跨生态(Android/鸿蒙+Linux)一站式AIOT应用开发平台。用比较简单的方式理解,我们平时编写训练模型,测试模型的时候,常用的是Linux/window系统。而实际部署到现场的时候,通常会以几种形态:GPU服务器、嵌入式设备(比如Android手机、人脸识别闸机等)、边缘设备。GPU服务器我们好理解,而Android嵌入式设备的底层芯片,通常是ARM架构。而Linux底层也是ARM架构,并且Android又是基于Linux内核开发的操作系统,两者可以共享Linux内核。因此就产生了从底层开发一套应用系统的方式,在此基础上同时带来原生Android和原生Linux使用体验。基于ARM芯片,阿加犀开发了Aidlux平台,可以在安卓手机上直接下载Aidlux使用。

再看一下常规的方式,我们想要将算法应用在手机Android时,需要将PC上编写的Python代码,封装成Android SO库(C++)。经过测试后,封装JNI调用SO库,最终在Android上使用Java调用JNI,最终再进行测试发布。因此我们可以看到,这样的流程需要一系列的工作人员参与,比如C++、Java、Python的工程师。但是大多数算法人员可能会用Python更多一些,比如上面编写的整套算法。Aidlux将其中的整个开发流程,全部打通,通过Aidlux平台,可以将PC端编写的代码,快速应用到Android系统上。

同时芯片对于AI算法的优化加速的能力。Aidlux内部一方面内置了多种深度学习框架,便于快速开发。另外对于多种算子进行了优化加速,很多算法的性能,也都能达到实时使用。

2.4.2平台使用

使用Aidlux主要有两种方式

(1)边缘设备的方式:阿加犀用高通芯片的S855,和S865制作了两款边缘设备,一款提供7T算力,一款提供15T算力

(2)手机设备的方式:没有边缘设备的情况下,也可以使用手机版本的Aidlux,尝试边缘设备的所有功能。

并且目前Aidlux已对基本市面上所有的芯片都进行了适配,在手机上运行算法模型,也可以体验优化的效果。当然在使用过程中,有个共同点,即手机设备和边缘设备采用的Aidlux软件,都是一样的。因此可以先尝试手机设备的方式,在后期需要更多算力的时候,使用边缘设备,就可以无缝衔接。打开安卓手机的应用商城,搜索Aidlux即可下载安装。具体使用方法可参加相关链接。

2.4.3算法部署

在平台上直接修改和运行代码不方便。而远程连接的方式有两种:基于Vscode的远程连接,和基于浏览器的远程连接。

基于浏览器的远程连接:

 基于Vscode的远程连接:

 安装环境:

conda create -n aidadao python=3.9
conda activate aidadao
pip install scikit-image
pip install opencv-python
pip install matplotlib

从参考连接下载代码。在测试视频中,180帧开始出现高空抛物。

移动端适配。在移动端,android端的代码在aidlux_highBuildingThrow文件夹中,在main文件中,导入opencv 使用同时我们使用的是cvs的方式来显示图片:

from cvs import *
path = "IMG_4550.MOV"
capture = cvs.VideoCapture(path) # 获取视频 
capture.set(cvs.CAP_PROP_POS_FRAMES, 200) # 设置视频从第几帧开始读取
frame = capture.read() # 读取初始视频帧
while True:
    frame = capture.read()
    if frame is None:
        break
cvs.imshow(frame)

 2.4.4应用展示

运行主函数:

## PC端运行
cd D:\code\aidlux_dd\高空抛物\highthrow_b
python main.py
## 移动端运行
cd /home/xly3dd/aidlux_highBuildingThrow
python main.py

PC端运行结果:

移动端运行结果:

 由上图可见,移动端运行速度相较PC端慢很多,这是由于测试手机端算力有限。

 修改代码中参数:

parser.add_argument("--max_age", help="Maximum number of frames to keep alive a track without associated detections.", type=int, default=1)
parser.add_argument("--min_hits", help="Minimum number of associated detections before track is initialised.", type=int, default=3)
parser.add_argument("--iou_threshold", help="Minimum IOU for match.", type=float, default=0.3)
class Sort(object):
    def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):

改变参数max_age:较小,跟踪容易丢失;较大,会保存过多冗余的状态。

sort = Sort(1, 5, 0.1) ## max_age:较小,跟踪容易丢失;较大,会保存过多冗余的状态

视频:

SVID_20230227_000906_1

改变参数min_hits:较小,容易误跟踪;较大,跟踪较迟钝。

sort = Sort(3, 9, 0.1) ## min_hits:较小,容易误跟踪;较大,跟踪较迟钝

视频:

SVID_20230227_215441_1

3车辆车牌识别

3.1背景简介

在检测到车后,再对车进行车牌的检测和识别。

3.2方案设计

开源车牌数据集是中科大的CCPD数据集,官网链接是:GitHub - detectRecog/CCPD: [ECCV 2018] CCPD: a diverse and well-annotated dataset for license plate detection and recognition

中科大车牌数据集有CCPD2019和CCPD2020,其中CCPD2019主要为蓝牌,CCPD2020为绿牌。

涉及算法:(车辆检测+)车牌检测+车牌识别

涉及模型:车牌检测的yolov5模型和车牌识别的LPRNet模型

车牌识别的方案主要有两种:一种是粗粒度的:车牌检测+车牌识别另外一种细粒度的:车牌检测+车牌矫正+车牌识别。

3.3算法实现

3.2.1划分数据集

按比例划分训练集和测试集

3.2.2训练

训练车牌检测模型:python tools/train_yolov5.py

得到模型:yolov5_best.pt

训练车牌识别模型:python train_lprnet.py

得到模型:lprnet_best.pth

3.2.3测试

修改各个脚本的权重文件路径,测试数据集路径等。

终端输入:python detect_yolov5.py

终端输入:python test_lprnet.py

将车牌检测+识别的算法串起来,即车牌检测+识别的pipeline,即为我们的代码的detect_torch_pipeline.py

修改中文字体:

## D:\code\aidlux_dd\3\code_plate_detection_recognization\utils\utils.py
font = ImageFont.truetype(r".\3\code_plate_detection_recognization\data\SimHei.ttf",sizes, encoding="utf-8")
image=change_cv2_draw(img,label,(int(c1[0]), int(c1[1]) - 30),30,[225,225,225])

##D:\code\aidlux_dd\3\code_plate_detection_recognization\detect_torch_pipeline.py
from utils.plots import colors##, plot_one_box
from utils.utils import plot_one_box
im0 = plot_one_box(xyxy, im0, label=label, color=colors(1, True), line_thickness=3)

测试图像:

cd code_plate_detection_recognization
python detect_torch_pipeline.py

3.4基于Aidlux平台的部署和推理测试

3.4.1车牌检测+识别模型的onnx序列化

export_yolov5.py

export_lprnet.py

同时用通过 detect_torch_pipeline.py 和detect_onnx_pipeline.py 运行同一张图片,结果显示如下:结果一致。

3.4.2车牌检测+识别模型的tflite的轻量化

修改对应的路径, 运行python export_tflite.py,完成车辆检测模型的tflite轻量化、车牌识别模型的tflite轻量化。

修改检测和识别的权重文件路径,以及输入图片路径和保存图片路径、运行 python detect_tflite_pipeline.py,输出结果图片。

3.4.3 车牌检测+识别的andorid端部署

修改对应的路径,在aidlux上运行:python aidlux/det_recog_aidlux_inference.py

手机端弹出图像检测页面,上面的显示中的中文显示乱码。

3.4.4中文字体修复

下载字体到代码相应位置,修改如下代码:

from PIL import Image,ImageDraw,ImageFont
def change_cv2_draw(image,strs,local,sizes,colour):
    cv2img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    pilimg = Image.fromarray(cv2img)
    draw = ImageDraw.Draw(pilimg)  # 图片上打印
    # font = ImageFont.truetype("./data/simhei.ttf",sizes, encoding="utf-8")
    font = ImageFont.truetype(r"/home/xly3dd/code_plate_detection_recognization/data/SimHei.ttf",sizes, encoding="utf-8")
    # draw.text((0,0),strs,colour,font=font)
    # font = ImageFont.truetype("simsun.ttc", 32, encoding="unic")
    # draw.text(local, strs, 'white', font=font)
    draw.text(local, strs, (128, 128, 128), font=font)
    image = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)
    return image

def plot_one_box_class(x, img, label=None, predstr=None, color=None,  line_thickness=3):
    # Plots one bounding box on image img
    tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
    color = color or [np.random.randint(0, 255) for _ in range(3)]
    c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
    cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if label:
        if predstr:
            tf = max(tl - 1, 1)  # font thickness
            t_size = cv2.getTextSize(predstr, 0, fontScale=tl / 3, thickness=tf)[0]
            c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
            cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
            # cv2.putText(img, predstr, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
            image=change_cv2_draw(img,label,(int(c1[0]), int(c1[1]) - 30),30,[225,225,225])
    # return cv2.cvtColor(np.asarray(img), cv2.COLOR_BGR2RGB)
    return image

3.4.5视频测试展示

修改输入方式为视频:

# source ="/home/xly3dd/code_plate_detection_recognization/demo/images"
path = "/home/xly3dd/code_plate_detection_recognization/demo/videos/VID_20230305_171410.mp4"
capture = cvs.VideoCapture(path) # 获取视频 
frame = capture.read()

det_model_path = "/home/xly3dd/code_plate_detection_recognization/weights/yolov5.tflite"
recog_model_path = "/home/xly3dd/code_plate_detection_recognization/weights/LPRNet_Simplified.tflite"
save_dir = "/home/xly3dd/code_plate_detection_recognization/detect"

index = 0
while True:
    frame = capture.read()
    index += 1
    if frame is None:
        break

    print(index)
    # image_ori = cv2.imread(os.path.join(source, img_name))
    image_ori = frame

测试显示:

测试视频:

20230306_020743

SVID_20230305_232642_1

参考链接

本文参考了Aidlux训练营中学习而来,详细代码可关注Aidlux公众号并回复关键词"智慧社区"获得。

其它同学的分享文章和心得也可在各技术网站搜索到。

阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IEEE&CS&AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值