【平头哥RVB2601创意应用开发】+火柴人播放器
[复制链接]
本帖最后由 tobot 于 2022-6-9 10:08 编辑
火柴人播放器
项目背景
原本是想借助RVB-2601的语音功能做一个可以自动报价和讨价还价的价格指示牌,后来发现实在搞不定w800的https功能,也就是说没法直接调用百度AI语音功能,进一步研究,试图采用本地语音,虽然使用已经经过高压缩的mp3格式,但转成文件后,常用的几句话就占满内存了(和网友们尝试的方法不一样,我这里扩内存是修改gcc_flash.ld的SRAM而不是改SPIFLASH),于是改换了方向,试图做一个火柴人播放器。
作品简介
火柴人(或者叫做稻草人),是在上个世纪八九十年代,显示器只有单色,屏幕分辨率比较低的时候,出现的一种图形化游戏,人物使用简笔画风格用几根线条表示躯干四肢,用圆圈表示头部,对比只有命令行的输入输出,这种游戏很是惊艳,但是随着技术发展,视窗取代了命令行风格,火柴人游戏很快没落了。但近年来像素游戏又有反弹趋势,于是考虑在RVB-2601上实现火柴人播放功能,让火柴人在一个128*64的屏幕上跳舞也算是怀旧吧。
系统框图
各部分功能说明和解析 视频源
在我的构想中,视频源可以是来自摄像头或者取用某一段录制视频,在本项目里面,按时间,将视频截取成单图片,再分别处理。
AI处理
将视频源中的图像转换成人物坐标,姿态识别本来是AI的常见应用,本来准备使用全志R329实现的,但调来调去没弄成,最后只好采用百度AI实现,为了配合这个项目,新做了一个界面,如下图:
百度AI可以将人体的动作、五官等都分别识别出来,但在我们这个项目中,并不需要那么多变量,一共只取用14组坐标(头、颈、左肩、左肘、左腕、右肩、右肘、右腕、左髋、左膝、左踝、右髋、右膝、右踝),根据上述坐标画出头部(根据头部位置,以指定半径画圆)、躯干(颈部到两髋之间)、左右手臂(连接肩肘腕)、左右腿(连接髋膝踝),在电脑上为了叠加原图显示,放大了5倍,显示区的大小是640*320。
将火柴人和原图叠加起来,可以看到基本能够匹配人物的肢体动作。
当然,也有不少出错的情况,比如:
在上图,MARiA手腕的位置就错了。
数组上传阿里云
阿里云的可视化开发非常方便,但目前暂时不需要这个功能,只需要提供一个转储的空间就行,阿里云是通过MQTT协议实现订阅和下发信息,因此最主要的是创建MQTT相关功能(地址、主题、用户、信息)。输入地址https://studio.iot.aliyun.com/createProject,基本是直接用鼠标就能实现配置,首先创建产品:
添加设备:
自定义功能,创建一个自定义的属性,在这里选择array,是为了配合RVB-2601的开发:
接下来就可以直接使用MQTT协议上传坐标数组了,对于阿里云来说,有一个现成的SDK工具,只需要pip install aliyun-iot-linkkit就可以很便捷的安装,但执行的时候可能会出现错误:
ImportError: cannot import name 'Iterable' from 'collections'
这和SDK工具无关,主要是python3.10改动了collections,找到文件
Python310\Lib\collections\__init__.py
增加
from collections.abc import *
就能修复这个错误。
如果不想用SDK工具,也可以自行配置MQTT参数:
1)首先是网址
在阿里云中,对于每个产品都有单独的网址,也就是
产品名.iot-as-mqtt.服务器.aliyuncs.com
产品名是刚才申请的,一般来说,服务器可以选择上海(cn-shanghai)。
2)ClientId
Clientid需要将多个字符串拼接起来,如:
mqttClientId="<ClientId>"+"|securemode=3,signmethod=hmacsha1|"
3)Username
拼接<DeviceName>和<ProductKey>。
mqttUsername = "<DeviceName>&<ProductKey>"
4)mqttPassword
把若干参数按字典键名排序,再把键名都拼接起来生成content,然后以DeviceSecret采用hma_sha1加密,转为十六进制字符串。
mqttPassword = hmac_sha1(DeviceSecret, content).toHexString()
当然,也可以直接在https://1024tools.com/hmac上快速获取,写进代码。
5)主题
主题就是刚申请的修改属性,由于我们再电脑端只需要发布这个属性值就行,同样的属性在RVB-2601上被订阅,于是RVB-2601就能根据这个主题来修改屏幕上的内容了。
作为MQTT的客户端,主要使用CONNECT、PUBLISH、SUBSCRIBE三个事件。
RVB-2601获取数据
包括配网和取值,使用AT指令集实现:
配网:
AT+WJAP=<ssid>,<password>
配置五元组鉴权信息:
AT+IDMAU="PRODUCT_KEY","DEVICE_NAME","DEVICE_SECRET","PRODUCT_SECRET"
具体配置方法小伙伴们都已经写得太多,这里不展开,后面源码里详细说明。
在阿里云中,消息使用JSON格式,简单说:键值对表示对象,对象在花括号内,数组在方括号内,数据用逗号分隔,很方便解析。
屏幕绘图
RVB-2601已经内置了lvgl,画线条非常简单,而对于一个火柴人来说,只要有手有脚,有躯干脑袋,就能看出是个人样,至于比例其实没有那么重要。可以将画人封装成单个函数,通过若干坐标直接生成人物图形,后面源码里详细说明。
人物姓名标签是从人物id生成的,因为在我这个项目里面,几个舞者的姓名都是已知固定的,所以直接将姓名固化为字体文件“lv_tobot_define.c”参与编译,实现方法在之前已做分享。
作品源码
考虑到该作品包括部分比较多,仅对RVB-2601上的几个主要函数做一个简短的介绍:
网络连接
网络使用w800,基本上是直接照抄例程。
在这里注册w800:
void wifi_w800_register(utask_t *task, w800_wifi_param_t *param)
{
int ret = w800_module_init(task, param);
if (ret < 0) {
LOGE(TAG, "driver init error");
return;
}
//run w800_dev_init to create wifi_dev_t and bind this driver
ret = driver_register(&w800_driver.drv, NULL, 0);
if (ret < 0) {
LOGE(TAG, "device register error");
}
memcpy(&w800_param, param, sizeof(w800_wifi_param_t));
}
向W800发送AT指令,实现配网和订阅主题功能
这部分代码需要自己写,但并不复杂。例如:
int w800_living_wjap(const char *myssid,const char *mypassword)
{
int ret = -1;
aos_mutex_lock(&g_cmd_mutex,AOS_WAIT_FOREVER);
atparser_clr_buf(g_atparser_uservice_t);
if (atparser_send(g_atparser_uservice_t, "AT+WJAP=%s,%s", myssid ,mypassword) == 0) {
if (atparser_recv(g_atparser_uservice_t, "OK\n") == 0) {
ret = 0;
} else {
printf("Destination Host Unreachable!\r\n");
}
}
atparser_cmd_exit(g_atparser_uservice_t);
if (ret == 0) {
printf("WIFI set OK!\r\n");
}
aos_mutex_unlock(&g_cmd_mutex);
return ret;
}
int w800_living_idmau(const char *mykey,const char *myname,const char *mysecret,const char *mypsecret)
{
int ret = -1;
aos_mutex_lock(&g_cmd_mutex,AOS_WAIT_FOREVER);
atparser_clr_buf(g_atparser_uservice_t);
if (atparser_send(g_atparser_uservice_t,"AT+IDMAU=\"%s\",\"%s\",\"%s\",\"%s\"", mykey , myname , mysecret ,mypsecret) == 0) {
if (atparser_recv(g_atparser_uservice_t, "OK\n") == 0) {
ret = 0;
}else {
printf("Destination Host Unreachable!\r\n");
}
}
atparser_cmd_exit(g_atparser_uservice_t);
if (ret == 0) {
printf("IoT set OK!\r\n");
}
aos_mutex_unlock(&g_cmd_mutex);
return ret;
}
int w800_living_idmcon(void)
{
int ret = -1;
aos_mutex_lock(&g_cmd_mutex,AOS_WAIT_FOREVER);
atparser_clr_buf(g_atparser_uservice_t);
if (atparser_send(g_atparser_uservice_t, "AT+IDMCON") == 0) {
if (atparser_recv(g_atparser_uservice_t, "OK\n") == 0) {
ret = 0;
} else {
printf("Destination Host Unreachable!\r\n");
}
}
atparser_cmd_exit(g_atparser_uservice_t);
if (ret == 0) {
printf("AT+IDMCON \r\n");
}
aos_mutex_unlock(&g_cmd_mutex);
return ret;
}
OLED的显示
初始化时实现OLED的调用,实际使用,可以直接拷贝内存内数据
void oled_init()
{
oled_pinmux_init();
oled_gpio_init();
oled_initialize();
// lv_disp porting
/*Create a display buffer*/
lv_disp_buf_init(&disp_buf1, buf1, buf2, 64 * 128);
/*Create a display*/
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
disp_drv.buffer = &disp_buf1;
disp_drv.flush_cb = oled_flush;
disp_drv.rotated = 0;
lv_disp_drv_register(&disp_drv);
}
画图
一直以来,画图可能是最麻烦的环节。通过结构体实现人物
struct dancer{
char name[20];
lv_obj_t * head;
lv_obj_t * body;
lv_obj_t * left_arm;
lv_obj_t * right_arm;
lv_obj_t * left_leg;
lv_obj_t * right_leg;
}
dancer[MAX_DANCERS];
视频演示
网络版的视频演示
只做了极乐净土
单机版的视频演示
极乐净土单机版:
红叶爱呗单机版: WeChat_20220609094104
原版视频
极乐净土: 极乐净土
枫叶爱歌:枫叶爱歌
项目总结
这次活动非常有意义,对于课题经过了反复尝试,知道最后几周才不得不承认一方面个人能力有限,一方面设备可用资源有限,不太可能做到项目申报时想做到的实时对话功能。不过管理员很给力,也鼓励我及时改换课题,当时想到了两个课题,一个是根据声音来拍照(录像),一个就是现在这个课题。前个课题的材料我也有现成的相机,只要写gpio就能实现,感觉没什么挑战性和趣味性,最终选择的是当前课题。
就我个人经验来看,拖延症的两个表现:在很短的时间(比如一两个小时)可以做完很长时间(比如一两周)的工作;很少量的工作(比如一两个小时)可以慢慢话很长时间(比如一两周)来完成。这两种情况,在我做任务时都出现过。
另外发现即使做了计划,但只要跑偏一个开头,后面的任务都歪到天边去了,比如为了研究文字的显示,专门开发了一个字模工具,但最终只需要使用其建立三个名字(日文显示);又比如本来只想研究一下如何使用,阿里云转存一个数组,为此几乎重写了网络相关的SDK(而且还bug多多)。
说实话,还有一些想法未能实现,程序中的bug也还没有改完,比如那个名字乱跳的问题。还有mqtt传输数组仍然感觉有点问题,现在的效率太低。另外大概率是阿里云的原因,不支持QoS2模式,也就是说类似我这种应用没法通过缓存实现云上的记录播放,只能实时传输播放,这也是为什么在视频效果中,网络版的图像卡顿和掉帧严重的原因。
当然瑕不掩瑜,阿里云和平头哥的无缝接入的确使得开发难度大幅降低,未来准备继续研究阿里云的图形化接口。
WeChat_20220609094104
|