C++ 实现 替代 OpenCV resize INTER_LINEAR 的一种方式

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);
相关推荐
十二2 小时前
【洛谷题解】P1083 [NOIP 2012 提高组] 借教室(二分答案 + 差分)
数据结构·c++·算法·蓝桥杯
D e re k ZH4 小时前
C++ 从零入门(一):基础语法与程序结构
数据结构·c++·算法
zjxtxdy5 小时前
C++面向对象程序开发-类与对象
开发语言·c++
東隅已逝,桑榆非晚7 小时前
算法6. 双指针法-查找总价格为目标的两个商品
c++·学习·算法
qq_406981037 小时前
c++拷贝构造函数
开发语言·c++
会飞锦鲤8 小时前
基于 SVM + HOG 的铸造缺陷检测系统
python·opencv·机器学习·计算机视觉·缺陷检测·图像分类·svm
H.莓飛10 小时前
【C++】【STL】vector_OJ题
linux·服务器·开发语言·数据结构·c++·visual studio
Rabitebla10 小时前
【Linux操作系统】进程篇(十一):进程程序替换——从底层原理到 exec 函数族
linux·服务器·c++·笔记·学习·算法
沫璃染墨10 小时前
从零入门计算机网络系列(十五):TCP可靠传输机制详解——从序号与确认应答到乱序处理
服务器·网络·c++·tcp/ip·tcp
青少儿编程课堂10 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:Bellman-Ford与SPFA含负权最短路及负环检测全解析
c++·python·算法·bfs·信息学竞赛