本帖最后由 TRIM 于 2024-9-24 16:22 编辑
灵感来源
在造物记发现一个帖子:现代科技与中国传统文化京剧变脸相结合
这个项目通过行空板K10的加速度传感器,实现每翻转一次行空板,屏幕上切换不同的脸谱图片
行空板上有人脸检测功能,那为何不做一个变脸相机呢?打开摄像头,检测到人脸,就朝着人脸那覆盖一个脸谱图片。每当“变脸”(人脸消失又出现)时,更换显示的图片。
制作过程
这看上去是一个非常简单的程序,我先做了一个简单的测试程序来试试:
马上,我便发现了两大问题:
1. 绘制上去的图片不支持透明通道,脸谱旁会有难看的黑框,如图所示:
2. 由于开启了屏幕后,对屏幕的任何操作都会自动刷新,所以,从清屏到重新绘制图片的这个间隔里,并不会显示图片,总体看上去一闪一闪的,达不到预期效果:
(图片先欠着)
发现原有的行空板库无法实现,我便查找了行空板库中使用的图形库:LVGL:一个轻量级嵌入式图形库
首先解决图片的透明通道显示问题!
LVGL是支持透明图片的,但是要在构建图片结构体时将header.cf设为LV_IMG_CF_TRUE_COLOR_ALPHA(带透明通道)
于是我改了改原来的函数
- void canvasDrawAlphaImage(int16_t x,int16_t y,int16_t w,int16_t h,const uint8_t* imageData) {
- lv_img_dsc_t image;
- image.header.always_zero = 0;
- image.header.w = w;
- image.header.h = h;
- image.data_size = w * h * 2;
- image.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; // 改了这里!
- image.data = imageData,
- k10.canvas->canvasDrawImage(x,y,&image);
- }
复制代码
但是,使用此函数,不能直接使用原有的图像数据,需要到LVGL的图片转换工具中,将图片转换成带透明通道的数组:
选择与代码中相同的格式,转换,得到了一个c文件!
里面的数组是这样的:
- const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_Y uint8_t y_map[] = {
- #if LV_COLOR_DEPTH == 1 || LV_COLOR_DEPTH == 8
- /*Pixel format: Alpha 8 bit, Red: 3 bit, Green: 3 bit, Blue: 2 bit*/
- //这里是一组图像数据,但行空板用不着
- #endif
- #if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 0
- /*Pixel format: Alpha 8 bit, Red: 5 bit, Green: 6 bit, Blue: 5 bit*/
- //这里是一组图像数据,但行空板也用不着
- #endif
- #if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP != 0
- /*Pixel format: Alpha 8 bit, Red: 5 bit, Green: 6 bit, Blue: 5 bit BUT the 2 color bytes are swapped*/
- //这里是一组图像数据,行空板用的是这组,将这组拿出,放到自己代码中即可
- #endif
- #if LV_COLOR_DEPTH == 32
- //这里还有一组图像数据,照样用不着
- #endif
- };
复制代码
取出数组,放到代码中,使用新的函数canvasDrawAlphaImage(x,y,w,h,imageData)绘制图片即可!
(把数组传入imageData中)
效果如图:
(图片先欠着)
接着,该解决图片闪烁的问题了!
我发现原来的库里,“清除屏幕”中,会将全屏清除,并且还会刷新一次屏幕。如果绘制图片后,仅擦除对应的区域,并且不刷新,图片的闪烁问题就会得到改善。
另外,原来的库使用xSemaphoreTake()和xSemaphoreGive()防止冲突,如果将图片的擦除和绘制同时在这两个语句之间执行,就不会有其他地方的强制刷新干扰了。
于是,我在原来的canvasDrawAlphaImage函数中加入了清除上一次绘制的图片代码
- void canvasDrawAlphaImage(lv_obj_t *_canvas, int16_t x, int16_t y, int16_t XToClean, int16_t YToClean, int16_t w, int16_t h, const uint8_t *imageData)
- {
- lv_draw_img_dsc_t img_dsc;
-
- xSemaphoreTake(xLvglMutex, portMAX_DELAY);
-
- // 分配缓冲区,里面存放100*138的透明图片数据用于擦除
- lv_color_t *_canvasNullBuf = (lv_color_t *)heap_caps_malloc(100 * 138 * sizeof(lv_color_t) * 8, MALLOC_CAP_SPIRAM); // MALLOC_CAP_SPIRAM;
- assert(_canvasNullBuf);
- memset(_canvasNullBuf, 0, 100 * 138 * sizeof(lv_color_t) * 8);
- // 将指定的区域擦除
- lv_canvas_copy_buf(_canvas, _canvasNullBuf, XToClean, YToClean, 100, 138);
- lv_canvas_set_px_opa(_canvas, XToClean, YToClean, LV_OPA_TRANSP);
-
- // 创建图像结构体
- lv_img_dsc_t image;
- image.header.always_zero = 0;
- image.header.w = w;
- image.header.h = h;
- image.data_size = w * h * 2;
- image.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; // 设置颜色格式:带透明通道
- image.data = imageData,
- // 绘制到指定区域
- lv_draw_img_dsc_init(&img_dsc);
- lv_canvas_draw_img(_canvas, x, y, &image, &img_dsc);
- // 这里再一并刷新
- lv_task_handler();
-
- xSemaphoreGive(xLvglMutex);
-
- // 释放内存
- heap_caps_free(_canvasNullBuf);
- }
复制代码
效果完美!
接下来,我还增加了RGB灯的显示,与脸谱的颜色同步变化,既可以让被拍摄者知道目前状态,又可以增添氛围感
效果展示
将行空板对准人脸,人脸上方覆盖脸谱,RGB灯同步颜色。当人脸被遮挡又出现后,切换脸谱及灯效
视频还是先欠着(拿不到手机拍不了视频)
代码下载
提示:
代码可复制到Mind+的“手动编辑”区,可直接上传
代码中的图片大小均为100x138
代码中包含较大的图片数据,如需复制到Mind+,建议选择已格式化图片数据的版本,以免出现卡死的情况
K10.zip
|