C++ 实现 替代 OpenCV resize INTER_LINEAR 的一种方式
flyfish
cpp
#include <opencv2/opencv.hpp>
#include <cmath>
#include <vector>
#include <algorithm>
// 如果编译器支持 OpenMP,取消下面注释可多核加速
// #include <omp.h>
static inline float pil_triangle(float x)
{
x = std::abs(x);
return (x < 1.0f) ? (1.0f - x) : 0.0f;
}
/**
* 近似匹配 PIL Image.BILINEAR 的可分离实现
* 支持 CV_8UC1 / CV_8UC3
* 缩小自动扩大滤波窗口(具备基本 anti-aliasing)
*/
cv::Mat pilBilinearResizeOptimized(const cv::Mat& src, int dst_w, int dst_h)
{
CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3);
CV_Assert(dst_w > 0 && dst_h > 0);
const int src_h = src.rows;
const int src_w = src.cols;
const int channels = src.channels();
if (src_w == dst_w && src_h == dst_h)
return src.clone();
// ========== 1. 水平方向滤波 (src -> temp) ==========
// 中间结果使用浮点,避免精度截断
cv::Mat temp(src_h, dst_w, CV_32FC(channels));
float scale_x = static_cast<float>(src_w) / dst_w;
float support_x = std::max(1.0f, scale_x);
// #pragma omp parallel for
for (int y = 0; y < src_h; ++y)
{
const uchar* src_row = src.ptr<uchar>(y);
float* temp_row = temp.ptr<float>(y);
for (int dx = 0; dx < dst_w; ++dx)
{
float center = (dx + 0.5f) * scale_x - 0.5f;
int x_min = std::max(0, static_cast<int>(std::floor(center - support_x + 1e-5f)));
int x_max = std::min(src_w - 1, static_cast<int>(std::ceil(center + support_x - 1e-5f)));
float sum[4] = {0.f, 0.f, 0.f, 0.f};
float w_sum = 0.f;
for (int sx = x_min; sx <= x_max; ++sx)
{
float w = pil_triangle((sx - center) / support_x);
if (w <= 0.f) continue;
const uchar* px = src_row + sx * channels;
for (int c = 0; c < channels; ++c)
sum[c] += px[c] * w;
w_sum += w;
}
float* out = temp_row + dx * channels;
if (w_sum > 1e-6f)
{
for (int c = 0; c < channels; ++c)
out[c] = sum[c] / w_sum;
}
else
{
// 极端情况兜底
int nearest = std::min(src_w - 1, std::max(0, static_cast<int>(std::round(center))));
const uchar* px = src_row + nearest * channels;
for (int c = 0; c < channels; ++c)
out[c] = static_cast<float>(px[c]);
}
}
}
// ========== 2. 垂直方向滤波 (temp -> dst) ==========
cv::Mat dst(dst_h, dst_w, src.type());
float scale_y = static_cast<float>(src_h) / dst_h;
float support_y = std::max(1.0f, scale_y);
// #pragma omp parallel for
for (int dy = 0; dy < dst_h; ++dy)
{
float center = (dy + 0.5f) * scale_y - 0.5f;
int y_min = std::max(0, static_cast<int>(std::floor(center - support_y + 1e-5f)));
int y_max = std::min(src_h - 1, static_cast<int>(std::ceil(center + support_y - 1e-5f)));
int win_size = y_max - y_min + 1;
std::vector<float> wy(win_size);
float wy_sum = 0.f;
// 预计算垂直权重,并只累加有效权重
for (int sy = y_min, idx = 0; sy <= y_max; ++sy, ++idx)
{
float w = pil_triangle((sy - center) / support_y);
wy[idx] = w;
if (w > 0.f)
wy_sum += w;
}
uchar* dst_row = dst.ptr<uchar>(dy);
for (int dx = 0; dx < dst_w; ++dx)
{
float sum[4] = {0.f, 0.f, 0.f, 0.f};
for (int sy = y_min, idx = 0; sy <= y_max; ++sy, ++idx)
{
float w = wy[idx];
if (w <= 0.f) continue;
const float* px = temp.ptr<float>(sy) + dx * channels;
for (int c = 0; c < channels; ++c)
sum[c] += px[c] * w;
}
uchar* out = dst_row + dx * channels;
if (wy_sum > 1e-6f)
{
for (int c = 0; c < channels; ++c)
out[c] = static_cast<uchar>(std::round(sum[c] / wy_sum));
}
else
{
// 极端情况兜底
int nearest = std::min(src_h - 1, std::max(0, static_cast<int>(std::round(center))));
const float* px = temp.ptr<float>(nearest) + dx * channels;
for (int c = 0; c < channels; ++c)
out[c] = static_cast<uchar>(std::round(px[c]));
}
}
}
return dst;
}
使用方式原来的 resize
在 preprocess 里把:
cpp
cv::resize(img, scaled, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR);
改成:
cpp
cv::Mat scaled = pilBilinearResizeOptimized(img, new_w, new_h);