基于PP-Human的人流量可视化数据大屏

使用django+pyecharts+PP-Human完成动态数据大屏的开发,目前完成了人流数据的采集与入库,PP-Human的其他功能:如属性识别等信息暂未接入大屏

1. 快速开始

1.1 环境配置

整个项目可以在项目挂载的数据集中下载,暂时还没有上传github,下载解压完成后

# 切换到解压目录
cd crowd_vis

# 我已经把依赖全部写在requirements.txt中,直接pip安装即可
# paddlepaddle没有写入,没有安装的话自行安装
pip install -r requirements.txt

1.2 启动服务

命令行运行:

python manage.py runserver

出现以下内容即启动成功

System check identified no issues (0 silenced).
August 19, 2022 - 22:53:05
Django version 3.2.15, using settings 'crowd_vis.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

随后启动你的浏览器,输入http://127.0.0.1:8000/即可访问

2. 视频流式传输解决方案

PP-Human其实目前并不支持流式传输,我这里用了一个挺憨的办法结合django的StreamingHttpResponse实现了“伪流式”,在前端的网页上确实是流式了(

2.1 PP-Human埋点

在pipeline.py文件中的predict_video函数的末尾加入

show_im = cv2.resize(im, (0, 0), fx=0.5, fy=0.5)
temp1, temp2 = cv2.imencode('.jpeg', show_im)
fp = open('frame.txt', 'wb')
fp.write(temp2.tobytes())
fp.close()
fp = open('records.txt', 'w')
ajax_data = records[-1]
ajax_data = [ajax_data, len(mot_res['boxes'])]
json.dump(ajax_data, fp)
fp.close()

整个PP-Human的pipeline其实就是一连串模型逐一对单个frame推理然后可视化,所以在函数的最后可以直接找到可视化结束的im变量,将他转换tobytes()之后,就可以传递给django处理了

2.2 Django流式传输

Django有StreamingHttpResponse流式响应类,这个类不同于普通的HttpResponse,他需要利用迭代器缓存数据。于是编写django视图读取之前埋点“偷”来的图片数据放入缓存其就实现了“伪”流式(在前端是真流式):以下是views.py中的流式传输部分

def pp_human_service():
    # PP-Human后台进程
    while True:
        pp_human_path = os.path.join(BASE_DIR, "pp-human", "pipeline", "pipeline.py ")
        yml_path = os.path.join(BASE_DIR, "pp-human", "pipeline", "config", "infer_cfg_pphuman.yml ")
        test_video_path = os.path.join(BASE_DIR, 'test1.mp4')
        # shell = r'python ' + pp_human_path + '--config ' + yml_path + r' --camera_id=0 --device=gpu --output_dir=output --do_entrance_counting'
        shell = r'python ' + pp_human_path + '--config ' + yml_path + r' --video_file=' + test_video_path + ' --device=gpu --output_dir=output --do_entrance_counting'
        subprocess.run(shell, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        # subprocess.run(shell)


t = threading.Thread(target=pp_human_service)
t.setDaemon(True)
t.start()


def video_display():
    # 流式视频传输迭代器
    txt_path = os.path.join(BASE_DIR, 'frame.txt')
    while True:
        fp = open(txt_path, 'rb')
        info = fp.read()
        fp.close()
        if info:
            yield b'--frame\r\n Content-Type: image/jpeg\r\n\r\n' + info + b'\r\n'


def video(request):
    # 使用流传输传输视频流
    return StreamingHttpResponse(video_display(), content_type='multipart/x-mixed-replace; boundary=frame')

3. 数据入库与数据动态刷新

3.1 数据入库

与前面伪流式的方法差不多,起一个定时的子进程收录从PP-Human埋点中采集的推理结果与信息。直接写在views.py里就可以,django在启动服务的时候会顺路把这些子进程都带起来。

def info_update_service():
    # 数据采集入库后台进程
    global context
    txt_path = os.path.join(BASE_DIR, 'records.txt')
    while True:
        try:
            fp = open(txt_path, 'r')
            info = json.load(fp)
            fp.close()
            vis_count = info[1]
            info = info[0]
            info = info[info.find("Total count: ") + 13:]
            total = eval(info[:info.find(',')])
            info = info[info.find(":") + 2:]
            in_count = eval(info[:info.find(',')])
            info = info[info.find(":") + 2:]
            out_count = eval(info[:-1])
            count0, count1, count2, count3, count4 = total % 10, total // 10 % 10, total // 100 % 10, total // 1000 % 10, total // 10000 % 10
            context = [total, vis_count, in_count, out_count, count0, count1, count2, count3, count4]
            db_obj = crowdinfo(total_count=total, in_count=in_count,
                               out_count=out_count,
                               vis_count=vis_count)
            db_obj.save()
            sleep(2)
        except Exception as e:
            sleep(2)
            print("db saving failed!")
            print(e)
            pass


t1 = threading.Thread(target=info_update_service)
t1.setDaemon(True)
t1.start()

3.2 数据动态实时刷新

数据动态刷新就是ajax的工作了,在django我们只需要写一个返回Json数据的view就可以。

这里使用pyecharts把各种数据表都整好之后一起扔给ajax处理。

def graph_vis(request):
    # 人流折线图,数据表格,饼图数据更新
    x_data = []
    vis_data = []
    in_data = []
    out_data = []
    table_data = []
    pie_data = []
    for info in crowdinfo.objects.all().order_by('-shoot_time')[:20]:
        if not pie_data:
            pie_data = [("滞留量", info.total_count-info.in_count-info.out_count),
                        ("流入量", info.in_count),
                        ("流出量", info.out_count)]
        x_data.append(info.shoot_time.strftime("%Y-%m-%d %H:%M:%S"))
        vis_data.append(info.vis_count)
        in_data.append(info.in_count)
        out_data.append(info.out_count)
        table_data.append([info.shoot_time.strftime("%y-%m-%d %H:%M:%S"), info.total_count, info.vis_count, info.out_count, info.in_count])
    data1 = (
        Line()
        .add_xaxis(x_data)
        .add_yaxis("进入人流", in_data, is_smooth=True, symbol_size=10, is_connect_nones=True, color='red',
                   linestyle_opts=opts.series_options.LineStyleOpts(width=3))
        .add_yaxis("出口人流", out_data, is_smooth=True, symbol_size=10, is_connect_nones=True, color='green',
                   linestyle_opts=opts.series_options.LineStyleOpts(width=3))
        .set_global_opts(legend_opts=opts.LegendOpts(textstyle_opts=opts.TextStyleOpts(color='white')),
                         xaxis_opts=opts.AxisOpts(type_='time',
                                                  axisline_opts=opts.AxisLineOpts(linestyle_opts=opts.LineStyleOpts(
                                                      color='white'))),
                         yaxis_opts=opts.AxisOpts(axisline_opts=opts.AxisLineOpts(linestyle_opts=opts.LineStyleOpts(
                             color="white"))),
                         )
        .dump_options_with_quotes()
    )
    data2 = (
        Line()
        .add_xaxis(x_data)
        .add_yaxis("在镜人流", vis_data, is_smooth=True, symbol_size=10, is_connect_nones=True, color='red',
                   linestyle_opts=opts.series_options.LineStyleOpts(width=3))
        .set_global_opts(legend_opts=opts.LegendOpts(textstyle_opts=opts.TextStyleOpts(color='white')),
                         xaxis_opts=opts.AxisOpts(type_='time',
                                                  axisline_opts=opts.AxisLineOpts(linestyle_opts=opts.LineStyleOpts(
                                                                                            color='white'))),
                         yaxis_opts=opts.AxisOpts(axisline_opts=opts.AxisLineOpts(linestyle_opts=opts.LineStyleOpts(
                                                                                            color="white"))),
                         )
        .dump_options_with_quotes()
    )
    data3 = (
        Table()
        .add(headers=['采集时间', '总人流', '在镜人流', '出口人流', '进口人流'],
             rows=table_data)
        .render('statics/render.html')
    )
    data3 = open('statics/render.html', 'r', encoding='utf-8').read()
    data3 = data3[data3.find("<table"):data3.find("</table>")]
    data3 = data3.replace('\n', '').replace('"', "'")
    data4 = (
        Pie()
        .add('', pie_data, center=['50%', '60%'], radius='70%')
        .set_global_opts(title_opts=opts.TitleOpts(title="人流状态分布",
                                                   title_textstyle_opts=opts.TextStyleOpts(color="white")),
                         legend_opts=opts.LegendOpts(orient='vertical', pos_left='right',
                                                     textstyle_opts=opts.TextStyleOpts(color='white')))
        .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
        .set_colors(["red", "yellow",  "pink", "orange", "purple"])
        .dump_options_with_quotes()
    )
    data1 = json.loads(data1)
    data2 = json.loads(data2)
    data3 = json.dumps(data3, ensure_ascii=False)
    data3 = data3.replace('"', '')
    data4 = json.loads(data4)
    data = {
        "code": 200,
        "msg": "success",
        "data": [data1, data2, data3, data4]
    }
    return JsonResponse(data)

ecarts是个JS库为什么要用python画好再传过去?因为这样更符合前后端分离的范式(不过是在给不想写Js找理由)。

一共有4个可视化数据表,前端JS代码如下:很短很方便,我不喜欢JS (

var chart1 = echarts.init(document.getElementById('graph1'),'roma');
var chart2 = echarts.init(document.getElementById('graph2'),'roma');
var chart4 = echarts.init(document.getElementById('graph4'),'light');
$(
    function () {
        fetchData(chart1,chart2,chart4);
        setInterval(fetchData,2000);
	}
);

function fetchData() {
    $.ajax({
        type: "get",
        url: "/graph_vis",
        dataType: 'json',
        success: function (result) {
        chart1.setOption(result.data[0]);
        chart2.setOption(result.data[1]);
        document.getElementById('graph3').innerHTML=result.data[2];
        chart4.setOption(result.data[3]);
        }
    });
}

差点忘了,还有一个数字大屏

function num_count(){
        // 朝后端发送ajax请求
            $.ajax({
            // 1.指定朝哪个后端发送ajax请求
            url:'/num_count', //不写就是朝当前地址提交【与form表单的action参数相同】
            // 2.请求方式
            type:'get',  // 不指定默认就是get,都是小写
            // 3.数据
            data:{},
            // 4.回调函数:当后端给你返回结果的时候会自动触发,args接受后端的返回结果
            success:function (args) {
                document.getElementById('count1').innerHTML=args[0];
                document.getElementById('count2').innerHTML=args[1];
                document.getElementById('count3').innerHTML=args[2];
                document.getElementById('count4').innerHTML=args[3];
                document.getElementById('num_count0').innerHTML=args[4];
                document.getElementById('num_count1').innerHTML=args[5];
                document.getElementById('num_count2').innerHTML=args[6];
                document.getElementById('num_count3').innerHTML=args[7];
                document.getElementById('num_count4').innerHTML=args[8];
            }
            })
    }
    setInterval("num_count()",1000);

最终效果:

在这里插入图片描述

4. 未来工作

  • 接入PP-Human的更多功能,如:属性识别、打架识别、摔倒识别等
  • 开发报警推送功能,结合打架、摔倒、人流过多等情况进行警告推送
  • 人流量数据时序相关,考虑接入机器学习的时序模型实现对人流的预警与预测
  • 少画饼,多摸鱼(???)
!unzip data/data165784/crowd_vis.zip 
%cd /home/aistudio/crowd_vis
!pip install -r requirements.txt
!python manage.py runserver 

此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐