目标检测 YOLOv5 - 预处理letterbox坐标映射回原图坐标 C++实现

flyfish

目标检测 YOLOv5 - 模型推理预处理 letterbox (python)
目标检测 YOLOv5 - 预处理letterbox坐标映射回原图坐标 (python)
目标检测 YOLOv5 - 模型推理预处理 letterbox C++实现
目标检测 YOLOv5 - 预处理letterbox坐标映射回原图坐标 C++实现

因为要记录比例所以比《目标检测 YOLOv5 - 模型推理预处理 letterbox C++实现》 多了参数 std::vector<float> &pad

cv::Mat letterbox(cv::Mat &src, int h, int w, std::vector<float> &pad)
{

    int in_w = src.cols; // width
    int in_h = src.rows; // height
    int tar_w = w;
    int tar_h = h;
    float r = std::min(float(tar_h) / in_h, float(tar_w) / in_w);
    int inside_w = round(in_w * r);
    int inside_h = round(in_h * r);
    int pad_w = tar_w - inside_w;
    int pad_h = tar_h - inside_h;

    cv::Mat resize_img;

    cv::resize(src, resize_img, cv::Size(inside_w, inside_h));

    pad_w = pad_w / 2;
    pad_h = pad_h / 2;

    pad.push_back(pad_w);
    pad.push_back(pad_h);
    pad.push_back(r);

    int top = int(round(pad_h - 0.1));
    int bottom = int(round(pad_h + 0.1));
    int left = int(round(pad_w - 0.1));
    int right = int(round(pad_w + 0.1));
    cv::copyMakeBorder(resize_img, resize_img, top, bottom, left, right, 0, cv::Scalar(114, 114, 114));

    return resize_img;
}

该坐标是 经过预处理letterbox图的坐标。
因为letter box的坐标,要映射回原图坐标
就要保存比例数据,原来在《 模型推理预处理 letterbox C++实现》里面的实现改进下
增加pad,类型是std::vector

原图的大小 in_w,in_h
letter box的大小 tar_w,tar_h
图像等比例缩放后的大小inside_w,inside_h

r就是原图要等比例放大还是缩小才能装入letter box的那个比例
要装进letter box,就记录宽度和高度各自变化了多少,因为一张图像,两边都边,所以记录一个边变化了多少就行
pad_w 记录一个边宽度的变化多少
pad_h 记录一个边高度的变化多少
最后用copyMakeBorder把变化的地方填充上灰色,就做好了
那么pad里面的数据就是pad_w ,pad_h ,r,有了这三个数据就能恢复了

映射回原图需要经过如下操作

    cv::Rect scaled_box;

    scaled_box.x = box.x - pad[0];
    scaled_box.y = box.y - pad[1];
    scaled_box.width = box.width;
    scaled_box.height = box.height;

是把 {letter box的大小 tar_w,tar_h }坐标变成
{图像等比例缩放后的大小inside_w,inside_h}坐标

float r = pad[2];

float x0 = 0;
float x1 = 0;
float y0 = 0;
float y1 = 0;

x0 = scaled_box.x / r;
y0 = scaled_box.y / r;
x1 = (scaled_box.x + scaled_box.width) / r;
y1 = (scaled_box.y + scaled_box.height) / r;

最后除以比例 r {图像等比例缩放后的大小inside_w,inside_h}坐标 变成原图坐标

void scale_box_1(cv::Rect box, int src_w, int src_h, std::vector<float> &pad)
{
    cv::Rect scaled_box;

    scaled_box.x = box.x - pad[0];
    scaled_box.y = box.y - pad[1];
    scaled_box.width = box.width;
    scaled_box.height = box.height;

    float r = pad[2];

    float x0 = 0;
    float x1 = 0;
    float y0 = 0;
    float y1 = 0;

    x0 = scaled_box.x / r;
    y0 = scaled_box.y / r;
    x1 = (scaled_box.x + scaled_box.width) / r;
    y1 = (scaled_box.y + scaled_box.height) / r;


    x0 = std::max(std::min(x0, (float)(src_w - 1)), 0.f);
    y0 = std::max(std::min(y0, (float)(src_h - 1)), 0.f);
    x1 = std::max(std::min(x1, (float)(src_w - 1)), 0.f);
    y1 = std::max(std::min(y1, (float)(src_h - 1)), 0.f);
    std::cout << "2:x0:" << x0 << "  y0:" << y0 << "  x1:" << x1 << "  y1:" << y1 << std::endl;
}

x0,y0 左上角坐标
x1,y1 右下角坐标

完整的代码

#include <iostream>
#include <algorithm>
#include <chrono>
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include <cmath>

cv::Mat letterbox(cv::Mat &src, int h, int w, std::vector<float> &pad)
{

    int in_w = src.cols; // width
    int in_h = src.rows; // height
    int tar_w = w;
    int tar_h = h;
    float r = std::min(float(tar_h) / in_h, float(tar_w) / in_w);
    int inside_w = round(in_w * r);
    int inside_h = round(in_h * r);
    int pad_w = tar_w - inside_w;
    int pad_h = tar_h - inside_h;

    cv::Mat resize_img;

    cv::resize(src, resize_img, cv::Size(inside_w, inside_h));

    pad_w = pad_w / 2;
    pad_h = pad_h / 2;

    pad.push_back(pad_w);
    pad.push_back(pad_h);
    pad.push_back(r);

    int top = int(round(pad_h - 0.1));
    int bottom = int(round(pad_h + 0.1));
    int left = int(round(pad_w - 0.1));
    int right = int(round(pad_w + 0.1));
    cv::copyMakeBorder(resize_img, resize_img, top, bottom, left, right, 0, cv::Scalar(114, 114, 114));

    return resize_img;
}
cv::Rect scale_box(cv::Rect box, std::vector<float> &pad)
{

    cv::Rect scaled_box;
    float r = pad[2];
    scaled_box.x = (box.x - pad[0]) / r;
    scaled_box.y = (box.y - pad[1]) / r;
    scaled_box.width = box.width / r;
    scaled_box.height = box.height / r;

    return scaled_box;
}

void scale_box_1(cv::Rect box, int src_w, int src_h, std::vector<float> &pad)
{
    cv::Rect scaled_box;

    scaled_box.x = box.x - pad[0];
    scaled_box.y = box.y - pad[1];
    scaled_box.width = box.width;
    scaled_box.height = box.height;

    float r = pad[2];

    float x0 = 0;
    float x1 = 0;
    float y0 = 0;
    float y1 = 0;

    x0 = scaled_box.x / r;
    y0 = scaled_box.y / r;
    x1 = (scaled_box.x + scaled_box.width) / r;
    y1 = (scaled_box.y + scaled_box.height) / r;


    x0 = std::max(std::min(x0, (float)(src_w - 1)), 0.f);
    y0 = std::max(std::min(y0, (float)(src_h - 1)), 0.f);
    x1 = std::max(std::min(x1, (float)(src_w - 1)), 0.f);
    y1 = std::max(std::min(y1, (float)(src_h - 1)), 0.f);
    std::cout << "2:x0:" << x0 << "  y0:" << y0 << "  x1:" << x1 << "  y1:" << y1 << std::endl;
}

int main()
{

    std::string image_path = "./test.jpg";
    int img_h = 640;
    int img_w = 640;
    cv::Mat src_img = cv::imread(image_path);
    int src_w = src_img.cols;
    int src_h = src_img.rows;
    cv::Mat img;
    std::vector<float> pad;
    cv::Mat boxed = letterbox(src_img, img_h, img_w, pad);
    cv::cvtColor(boxed, img, cv::COLOR_BGR2RGB);
    cv::imwrite("letterbox.jpg", boxed);

    // 这里进行 检测处理 x0,y0,x1,y1 是其中一个结果
    float x0 = 70;
    float x1 = 244;
    float y0 = 345;
    float y1 = 496;

    cv::Rect box(cv::Point(x0, y0), cv::Point(x1, y1));
    cv::Rect scaled_box = scale_box(box, pad);
    // 已经a还原了坐标 剩下转换成自己需要的坐标格式,例如 left,top,right,bottom

    x0 = scaled_box.x;
    y0 = scaled_box.y;
    x1 = scaled_box.x + scaled_box.width;
    y1 = scaled_box.y + scaled_box.height;

    // 适合图像
    x0 = std::max(std::min(x0, (float)(src_w - 1)), 0.f);
    y0 = std::max(std::min(y0, (float)(src_h - 1)), 0.f);
    x1 = std::max(std::min(x1, (float)(src_w - 1)), 0.f);
    y1 = std::max(std::min(y1, (float)(src_h - 1)), 0.f);

    std::cout << "1:x0:" << x0 << "  y0:" << y0 << "  x1:" << x1 << "  y1:" << y1 << std::endl;

    scale_box_1(box, src_w, src_h, pad); //测试
    return 0;
}
04-27 06:12