| 本帖最后由 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 
 
 
 
 |