目标检测 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;
}