一、前言
在物联网的诸多场景中,除了传感器是AIoT设备中的重要组成外,摄像头作为视觉输入的关键设备,在一些AI应用场景或者监控场景也是必不可少的,本文将带大家一起来给HaaS100开发板装上千里眼,并输出到屏幕显示,充分挖掘HaaS100的硬件能力,帮助你实现更多视觉业务场景应用。监控老板,监控xxx等等,充分发挥你的想象力吧!
二、方案简介
2.1 方案组成
整个方案由HaaS100、WiFi摄像头、LCD组成。LCD与HaaS100通过SPI连接,HaaS100通过Http请求获取到JPEG数据最终显示到LCD上。
2.2 WiFi摄像头选型
市面上的WiFi摄像头比较多,在本例中WiFi摄像头采用ESP官方的ESP32-EYE进行适配,ESP32-CAM是ESP32第三方厂商开发的一款低成本方案,应用也比较广泛,开发者也可以选择它作为方案之一,万能的淘宝上有很多卖家,商家也会提供相应的资料,购买链接如下:
ESP32-EYE: https://detail.tmall.com/item.htm?spm=a230r.1.14.1.150d6a6ftZ6h4K&id=611790371635&ns=1&abbucket=3
ESP32-CAM: https://detail.tmall.com/item.htm?spm=a230r.1.14.1.3f543b21XaGDay&id=581256720864&ns=1&abbucket=3
2.3 数据处理流程
HaaS100通过http请求到JPEG数据后,通过jpeg解码为RGB888数据,因为SPI LCD是RGB565格式,在送显前就需要将RGB888的数据通过格式转换模块转为RGB565格式。
三、方案实践
3.1 LCD适配
LCD的驱动适配请参考《HaaS100复古机来了》。
购买链接https://item.taobao.com/item.htm?spm=a1z09.2.0.0.768d2e8d9D3S7s&id=38842179442&_u=m1tg6s6048c2
3.2 ESP32-EYE开发配置
3.2.1 环境搭建
代码下载
$git clone --recursive https://github.com/espressif/esp-who.git
Python环境创建
这一个步骤不是必须的,不过如果你有多个python环境的需求,也安装过conda可以使用该步骤为esp32的开发创建一个独立的python开发环境,避免不同开发环境的相互影响。
$conda create -n esp32 python=3.8
ESP-IDF安装
不同的操作系统安装的步骤也有所差异,请参考官网文档进行安装:
环境变量设置
这里以Macbook为例进行环境变量设置:
-
$cd ~/esp/esp-idf
-
$./install.sh
-
$chmod . $HOME/esp-who/esp-idf/export.sh
-
$source . $HOME/esp-who/esp-idf/export.sh
注意:
每次重启终端后都需要执行该步骤,否则找不到idf.py命令,或者可以加入到根目录.bashrc中不用每次再输入该命令。
代码编译
ESP32-EYE的代码中提供了多个Demo,使用camera_web_server来建立一个web服务器,该Demo中摄像头采集的数据以mjpeg格式提供,并且提供了以http请求的方式获取mjpeg/jpeg图像数据。编译需要进入到Demo的目录中:
-
$cd examples/single_chip/camera_web_server/
-
$idf.py build
代码烧录
$idf.py -p [port] flash
例如:
idf.py -p /dev/cu.SLAB_USBtoUART flash
Log监视器
查看串口log,进入到camera_web_server所在目录执行。
$idf.py -p [port] monitor
例如:
idf.py -p /dev/cu.SLAB_USBtoUART monitor
所以camera wifi的IP就是192.168.3.135。
3.2.2 ESP32 EYE网络设置
SoftAP模式
默认启动后ESP32 EYE已经开启了SSID为ESP32-Camera的AP,可以使用电脑连接该AP。
也可以通过修改sdkconfig来改变ssid/password、station连接数量、AP信道、服务器IP等,然后重新进行编译:
Station模式
ESP32也支持station与SoftAP模式共存,比如想让ESP32 EYE接入到SSID为haas_test的局域网中,修改sdkconfig中的ssid/password即可。
3.2.3 电脑访问获取图像
为了确认ESP32-EYE摄像头是否正常,先通过电脑方式查看web界面
http://192.168.4.1/:
直接抓取流http://192.168.4.1:81/stream:
抓取当前画面http://192.168.4.1/capture:
3.3 HaaS100开发
3.3.1 代码下载
参考《HaaS100快速开始》下载AliOS Things代码。
3.3.2 代码流程分析
代码下载后,wificamera_demo的代码路径:
$application/example/wificamera_demo
获取图像
LCD是320*240的屏,为了降低数据传输带宽的占用,将ESP32-EYE的摄像头数据采集分辨率相应的修改为QVGA(320*240),同时当HaaS100接收到图像数据后不需要进行图像的裁剪处理,减少了CPU资源开销:
在app_entry.c中初始化时注册了一个WiFi事件处理函数:
-
int application_start(int argc, char **argv)
-
{
-
......
-
set_iotx_info();
-
netmgr_init();
-
......
-
aos_register_event_filter(EV_WIFI, wifi_service_event, NULL);
-
......
-
-
return 0;
-
}
当网络连接成功后,会进入到以下函数完成初始化及WiFi摄像头连接并获取图像数据的线程,另外也会进行物联网平台的连接。
-
static void wifi_service_event(input_event_t *event, void *priv_data)
-
{
-
if (event->type != EV_WIFI) {
-
return;
-
}
-
-
if (event->code != CODE_WIFI_ON_SNTP_OK)
-
return;
-
-
if (!linkkit_started) {
-
LOG("start to do ucloud_ai_demo\n");
-
if (ucloud_ai_init()
< 0)
-
return;
-
-
aos_task_new("wificamera_process",wificamera_main, NULL, 1024*10);
-
aos_task_new("linkkit", (void (*)(void *))linkkit_main, NULL, 1024 * 10);
-
linkkit_started = 1;
-
}
-
}
网络连接成功后进入到http_stream_process中调用http_get获取JPEG数据:
这里url参数就是WiFi摄像头的Web Server地址,这里通过capture方式获取图片进行显示,例如只需要传入http_stream_process("http://192.168.4.1:80/capture")即可获取JPEG图像。
-
int ucloud_ai_main(void *p)
-
{
-
int ret = 0;
-
int recv_len = 0;
-
char * customer_header = "Accept: */*\r\n";
-
FILE *jpegFile = NULL;
-
char *url = WIFICAMERA_URL;
-
char *upload_url = NULL;
-
-
LOG("start ucloud_ai_main\n");
-
/*for wificamera client*/
-
ret = httpclient_prepare(&wificamera_client_data, HEAD_SIZE, BODY_SZIE);
-
if (ret != HTTP_SUCCESS)
-
return -1;
-
-
wificamera_client.is_http = true;
-
httpclient_set_custom_header(&wificamera_client, customer_header);
-
ret = httpclient_conn(&wificamera_client, (const char *)url);
-
if(HTTP_SUCCESS != ret) {
-
LOGE(TAG, "http connect failed");
-
return -1;
-
}
-
-
/*for ai client*/
-
ret = httpclient_prepare(&ai_client_data, HEAD_SIZE, BODY_SZIE);
-
if (ret != HTTP_SUCCESS)
-
return -1;
-
ai_client.is_http = true;
-
httpclient_set_custom_header(&ai_client, customer_header);
-
......
-
-
while (1) {
-
ret = http_get_image(&wificamera_client, &wificamera_client_data, url);
-
if (ret
<= 0) {
-
LOGE(TAG, "http_get_image fail\n");
-
continue;
-
}
-
.......
-
}
获取一张图片的函数具体实现:
-
static int32_t http_get_image(httpclient_t *client, httpclient_data_t *client_data, char *url)
-
{
-
int ret;
-
int recv_len = 0;
-
-
httpclient_reset(client_data);
-
ret = httpclient_send(client, (const char *)url, HTTP_GET, client_data);
-
if(HTTP_SUCCESS != ret) {
-
LOGE(TAG, "http send request failed");
-
return -1;
-
}
-
do {
-
ret = httpclient_recv(client, client_data);
-
if (ret
< 0)
-
break;
-
recv_len =
client_data->response_content_len;
-
} while (ret == HTTP_EAGAIN);
-
return recv_len;
-
}
存储图像数据
在获取到一张图像后,把该图片保存到/data目录,实现函数如下:
-
static int32_t save_captured_image(char *buf, int len, char *path)
-
{
-
FILE *jpegFile;
-
-
if ((jpegFile = fopen(path, "wb")) == NULL) {
-
LOGE(TAG, "opening output file fail\n");
-
return -1;
-
}
-
if (fwrite(buf, len, 1, jpegFile)
< 1) {
-
LOGE(TAG, "write buf fail\n");
-
return -1;
-
}
-
fclose(jpegFile);
-
return 0;
-
}
在调试过程中,存储照片到sdcard可以帮助我们确认抓到的图片是否正常,存储到sdcard的路径是/sdcard/capture.jpg。
解码JPEG图像
在本Demo中实现了两种方式解码图片。使用A方式可以直接从文件系统中加载文件,使用B方式直接解码内存中的JPEG Buffer。
A. 直接使用SDL解码图片并显示,参数传入图片路径:
-
int graphics_draw_image(const char *file, int x, int y)
-
{
-
SDL_Rect drect = { x, y, 0, 0 };
-
if(strcmp(file, "") == 0) return;
-
for(int i = 0; i
< image_count; i++) {
-
if(strcmp(file, image[i].file) != 0) continue;
-
graphics_draw_texture(image[i].texture, x, y);
-
return 0;
-
}
-
graphics_generate_image(file);
-
graphics_draw_texture(image[image_count-1].texture, x, y);
-
return 0;
-
}
B. 使用libjpeg-turbo解码器,通过封装后将JPEG图片解码为RGB888格式数据输出:
-
int tjpeg2rgb(unsigned char* jpeg_buffer, int jpeg_size, unsigned char* rgb_buffer, int* size)
-
{
-
int ret = 0;
-
tjhandle handle = NULL;
-
int width, height, subsample, colorspace;
-
int flags = 0;
-
int pixelfmt = TJPF_RGB;
-
int pitch = 0;
-
-
handle = tjInitDecompress();
-
if (!handle) {
-
LOGE(TAG, "tjInitDecompress fail, ret = %d", ret);
-
return -1;
-
}
-
ret = tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);
-
if (ret
< 0) {
-
LOGE(TAG, "tjDecompressHeader3 fail, ret = %d", ret);
-
goto finish;
-
}
-
LOG("width: %d, height: %d", width, height);
-
flags |= 0;
-
if ((rgb_buffer = (unsigned char *)tjAlloc(width * height *
-
tjPixelSize[pixelfmt])) == NULL) {
-
LOGE(TAG, "allocating uncompressed image buffer");
-
goto finish;
-
}
-
*size = width * height * tjPixelSize[pixelfmt];
-
pitch = tjPixelSize[pixelfmt] * width;
-
ret = tjDecompress2(handle, jpeg_buffer, jpeg_size, rgb_buffer, width, pitch,
-
height, pixelfmt, flags);
-
if (ret < 0) {
-
LOGE(TAG, "tjDecompress2 fail, ret = %d", ret);
-
tjFree(rgb_buffer);
-
}
-
-
finish:
-
tjDestroy(handle);
-
return ret;
-
}
图像格式转换
因为屏幕是RGB565格式,需要将图像进一步转换为RGB565格式。
-
int rgb888torgb565(unsigned char* rgb888_buf, int rgb888_size, unsigned short *rgb565_buf, int rgb565_size)
-
{
-
int i = 0;
-
unsigned char Red = 0;
-
unsigned char Green = 0;
-
unsigned char Blue = 0;
-
int count = 0;
-
-
if(rgb888_buf == NULL || rgb888_size
<= 0 || rgb565_buf == NULL || \
-
rgb565_size <= 0 || (rgb565_size < (rgb888_size/3)*2)) {
-
printf("Invail input parameter in %s\n", __FUNCTION__);
-
return -1 ;
-
}
-
-
for(i = 0; i<rgb888_size; i += 3) {
-
Red = rgb888_buf[i] >> 3;
-
Green = rgb888_buf[i+1] >> 2;
-
Blue = rgb888_buf[i+2] >> 3;
-
rgb565_buf[count++] = ((Red
<<11)|(Green<<5)|(Blue));
-
}
-
return count;
-
}
显示图像画面
同样在显示图像画面也有两种方式实现。
A. 使用SDL显示:
-
void graphics_flip() {
-
SDL_RenderPresent(renderer);
-
SDL_RenderClear(renderer);
-
SDL_DestroyTexture(image_texture);
-
}
B. 使用LCD hal接口实现:
-
/*show picture on lcd screen*/
-
hal_lcd->lcd_frame_draw(framebuffer);
代码在前面的http_stream_process中实现,使用该方式直接调用lcd hal接口,如果不想通过SDL实现更丰富的UI相关功能,采用该方式的效率更高。
3.3.3 代码编译
-
$aos make distclean
-
$aos make wificamera_demo@haas100 -c config
-
$aos make
3.3.4 代码烧录
如果是使用的Window烧录工具参考《HaaS100快速开始》,烧录的文件位于:
$out/wificamera_demo@haas100/binary/wificamera_demo@haas100.bin
将文件替换到write_flash_gui/ota_bin/ota_rtos.bin。
如果使用的是docker环境参考《一步搞定AliOS Things开发环境安装》4.3烧录固件。
3.3.5 网络连接
按照2.3配置ESP32-EYE后,我们可以通过SoftAP方式直接连接ESP32-EYE,或者将ESP32-EYE和HaaS100连接统一个路由器。
A. HaaS100连接ESP32 SoftAP:
$netmgr -t wifi -c ESP32-Camera
使用该方法,使用http_stream_process("http://192.168.4.1:80/capture")获取图像数据。
B. HaaS100连接路由器:
$netmgr -t wifi -c haas_test 12345678
ssid/password根据自己路由器配置进行修改。连接路由器后,就需要通过ESP32-EYE的串口Log来确认相应的IP是多少,如3.2.1.7看到连接路由器后的IP是192.168.3.135,然后填入到http_stream_process入口参数中。网络连接成功后就可以在LCD屏上看到画面了。
四、总结
WiFi摄像头因为网络具有一定的延迟,帧率在5~10帧,对于延迟要求不高的场合是可以满足需求的。后续会加入USB或SPI本地摄像头降低延迟丰富业务场景。喜欢本文的朋友可以点赞收藏,评论区回复交流哦,谢谢。
五、开发者技术支持
如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号
更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/
转载:https://blog.csdn.net/HaaSTech/article/details/113253288