letterbox的两种实现

letterbox的两种实现 Marc 2023-11-03 10:54:34 1072

以前经常觉得resize你的图像成模型宽高, 送进去, 对推理精度影响不大, 最近发现, 还真的挺大.
这里涉及一个概念, 就是letterbox.

因为我们的模型放入yolov5做推理的时候, 模型输入是640x640的正方形, 你需要把图片做一个预处理:

例如这张图
比较宽, 那么有两种做法, 一种粗暴的, 直接跟揉面一样, 两边一夹:

这个就叫做resize, 就是直接变形成了640x640
这样会导致最终精度的下降,所以最推荐的做法是:

首先他图片的宽高中大的那一个, 缩放成640, 然后等比例, 缩放另一个方向, 然后空余部分填充灰色或者黑色, 比如这种扁扁的图片, 就处理成:

再送入模型中做推理.

首先, 在目前不纠结速度跟效率的前提下, 有两个方法实现:

  1. opencv
  2. 纯手搓

当然还有用rga,vpss等等做法, 回头再一一列举, 先说说这两种

先说说opencv的做法:
rk的npu2 example中就有这个函数:


void letterbox(const cv::Mat &image, cv::Mat &padded_image, BOX_RECT &pads, const float scale, const cv::Size &target_size, const cv::Scalar &pad_color)
{
    // 调整图像大小
    cv::Mat resized_image;
    cv::resize(image, resized_image, cv::Size(), scale, scale);

    // 计算填充大小
    int pad_width = target_size.width - resized_image.cols;
    int pad_height = target_size.height - resized_image.rows;

    pads.left = pad_width / 2;
    pads.right = pad_width - pads.left;
    pads.top = pad_height / 2;
    pads.bottom = pad_height - pads.top;

    // 在图像周围添加填充
    cv::copyMakeBorder(resized_image, padded_image, pads.top, pads.bottom, pads.left, pads.right, cv::BORDER_CONSTANT, pad_color);
}

简单吧..

如果手搓的话, 分三步:

  1. 缩小为640x360,
  2. 新建一个画布640x640, 填充灰边,
  3. 在画面中间填入数据
void VideoChannel::resize_from_1080p_to_640_square(char *input, char *output) {

    char *resizedData = (char *) malloc(TARGET_WIDTH * TARGET_HEIGHT * ORIGINAL_CHANNELS);
    // LOGD("resize from 1080p to 640 square");
    // 缩小图片
    resizeImage(input, resizedData);
    // 128就是灰色, 全部像素设置为灰色
    memset(output, 128, OUTPUT_WIDTH * OUTPUT_HEIGHT * RGB_CHANNELS);
    // 填充到640x640的灰度图中
    fillGrayImage(output, resizedData, OUTPUT_WIDTH, OUTPUT_HEIGHT);
    // 申请的内存释放掉
    free(resizedData);
}

缩放图像:

// 缩放图像
void VideoChannel::resizeImage(char *input, char *output) {
    int channel = 3;
    for (int y = 0; y < TARGET_HEIGHT; y++) {
        for (int x = 0; x < TARGET_WIDTH; x++) {
            int origX = x * channel * ORIGINAL_WIDTH / (channel * TARGET_WIDTH);
            int origY = y * ORIGINAL_HEIGHT / TARGET_HEIGHT;

            for (int c = 0; c < channel; c++) {
                output[(y * TARGET_WIDTH + x) * channel + c] = input[(origY * ORIGINAL_WIDTH + origX) * ORIGINAL_CHANNELS + c];
            }
        }
    }
}
// 将缩小后的图像填充到全灰色图像中
void VideoChannel::fillGrayImage(char *grayData, char *resizedData, int width, int height) {

    // 从140行开始填充, 因为上面要留下灰边
    char *grayDataPtr = grayData + BORDER_SIZE * width * RGB_CHANNELS;
    char *resizedDataPtr = resizedData;
    for (int i = 0; i < TARGET_WIDTH * TARGET_HEIGHT; i++) {
        memcpy(grayDataPtr, resizedDataPtr, RGB_CHANNELS);
        grayDataPtr = grayDataPtr + RGB_CHANNELS;
        resizedDataPtr = resizedDataPtr + RGB_CHANNELS;
    }
}

最后再多嘴一句, 1106上面已经抄了海思的vpss了, 估计也是拿rga的核心, 做了这类转换工作, 所以它可以从vi->vpss, 然后从vpss拿到一个640x360的图像, 只需要简单的复制内存到640x640的灰色内存中即可.

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Marc
红包 12 6 评论 打赏
评论
4个
内容存在敏感词
手气红包
  • 来自远方 2023-11-06 11:01:44
    5.00元
    回复
    不错哦~
  • automan 2023-11-03 15:53:12
    回复
    收藏加关注,追番不迷路
  • Stranger 2023-11-03 15:52:11
    回复
    文章不错
  • 亮仔 工作微信[呲牙] 2023-11-03 11:00:38
    回复
    收藏加关注,追番不迷路
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Marc
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区