当前位置: 首页 > article >正文

双线性插值算法:原理、实现、优化及在图像处理和多领域中的广泛应用与发展趋势(二)

 五、图像金字塔和多尺度分析

双线性插值在图像金字塔的构建中也发挥着重要的作用。图像金字塔是一种多分辨率表示图像的结构,通常包括一个原始图像的不同分辨率的版本。在构建图像金字塔时,我们可以通过不断地对图像进行下采样(缩小)来得到一系列分辨率逐渐降低的图像。双线性插值可以用于上采样(放大)操作,在金字塔的上层重建更高分辨率的图像。

以下是使用双线性插值构建图像金字塔的示例代码:

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

typedef cv::Point3_<uint8_t> Pixel;

// 双线性插值算法
void bilinearInterpolation(Mat& src, Mat& dst, double sx, double sy) {
    int dst_rows = static_cast<int>(src.rows * sy);
    int dst_cols = static_cast<int>(src.cols * sx);
    dst = Mat::zeros(cv::Size(dst_cols, dst_rows), src.type());

    dst.forEach<Pixel>([&](Pixel &p, const int * position) -> void {
        int row = position[0];
        int col = position[1];

        // (col,row)为目标图像坐标
        // (before_x,before_y)原图坐标
        double before_x = double(col + 0.5) / sx - 0.5f;
        double before_y = double(row + 0.5) / sy - 0.5;
        // 原图像坐标四个相邻点
        // 获得变换前最近的四个顶点,取整
        int top_y = static_cast<int>(before_y);
        int bottom_y = top_y + 1;
        int left_x = static_cast<int>(before_x);
        int right_x = left_x + 1;

        //计算变换前坐标的小数部分
        double u = before_x - left_x;
        double v = before_y - top_y;

        // 如果计算的原始图像的像素大于真实原始图像尺寸
        if ((top_y >= src.rows - 1) && (left_x >= src.cols - 1)) {//右下角
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k] = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k];
            }
        } else if (top_y >= src.rows - 1) { //最后一行
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * u * src.at<Vec3b>(top_y, right_x)[k];
            }
        } else if (left_x >= src.cols - 1) {//最后一列
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k];
            }
        } else {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * (u) * src.at<Vec3b>(top_y, right_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k]
                          + (u) * (v) * src.at<Vec3b>(bottom_y, right_x)[k];
            }
        }
    });
}


int main() {
    Mat src = imread(".../grass.jpg");
    imshow("src", src);

    vector<Mat> pyramid;
    pyramid.push_back(src);
    int levels = 3;  // 金字塔层数
    double scale = 0.5;  // 缩放因子
    Mat temp = src;
    for (int i = 0; i < levels; ++i) {
        Mat downsampled;
        resize(temp, downsampled, Size(), scale, scale, INTER_LINEAR);
        pyramid.push_back(downsampled);
        temp = downsampled;
    }


    // 从金字塔中重建图像
    Mat reconstructed;
    bilinearInterpolation(pyramid.back(), reconstructed, pow(1 / scale, levels), pow(1 / scale, levels));
    imshow("Reconstructed", reconstructed);


    waitKey(0);
    return 0;
}

在这个示例中,我们首先将图像进行多次下采样,并存储在 pyramid 向量中。然后使用双线性插值从金字塔的最底层(最小分辨率的图像)重建原始图像。通过下采样和上采样的过程,可以用于图像的多尺度分析,如特征检测、图像匹配等任务。在图像匹配中,金字塔可以用于在不同尺度下寻找特征,以实现尺度不变性。

六、图像处理中的坐标变换

双线性插值不仅仅局限于图像的缩放,它在各种坐标变换中都非常有用。例如,在透视变换中,将图像从一个视角转换到另一个视角时,像素的位置会发生非线性的变化,通常会使用双线性插值来计算变换后的像素值。以下是一个简单的透视变换示例,结合双线性插值:

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;


typedef cv::Point3_<uint8_t> Pixel;


// 双线性插值算法
void bilinearInterpolation(Mat& src, Mat& dst, double sx, double sy) {
    int dst_rows = static_cast<int>(src.rows * sy);
    int dst_cols = static_cast<int>(src.cols * sx);
    dst = Mat::zeros(cv::Size(dst_cols, dst_rows), src.type());


    dst.forEach<Pixel>([&](Pixel &p, const int * position) -> void {
        int row = position[0];
        int col = position[1];


        double before_x = double(col + 0.5) / sx - 0.5f;
        double before_y = double(row + 0.5) / sy - 0.5;
        int top_y = static_cast<int>(before_y);
        int bottom_y = top_y + 1;
        int left_x = static_cast<int>(before_x);
        int right_x = left_x + 1;


        double u = before_x - left_x;
        double v = before_y - top_y;


        if ((top_y >= src.rows - 1) && (left_x >= src.cols - 1)) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k] = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k];
            }
        } else if (top_y >= src.rows - 1) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * u * src.at<Vec3b>(top_y, right_x)[k];
            }
        } else if (left_x >= src.cols - 1) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k];
            }
        } else {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * (u) * src.at<Vec3b>(top_y, right_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k]
                          + (u) * (v) * src.at<Vec3b>(bottom_y, right_x)[k];
            }
        }
    });
}


int main() {
    Mat src = imread(".../grass.jpg");
    imshow("src", src);


    Point2f src_pts[4] = { Point2f(0, 0), Point2f(src.cols - 1, 0), Point2f(src.cols - 1, src.rows - 1), Point2f(0, src.rows - 1) };
    Point2f dst_pts[4] = { Point2f(50, 50), Point2f(src.cols - 100, 100), Point2f(src.cols - 150, src.rows - 50), Point2f(100, src.rows - 100) };


    Mat transform_matrix = getPerspectiveTransform(src_pts, dst_pts);
    Mat warped;
    warpPerspective(src, warped, transform_matrix, src.size());


    Mat final_result;
    bilinearInterpolation(warped, final_result, 1.0, 1.0);


    imshow("Warped", warped);
    imshow("Final Result", final_result);


    waitKey(0);
    return 0;
}

在这个示例中,我们首先使用 getPerspectiveTransform 函数计算透视变换矩阵,将图像进行透视变换,然后使用双线性插值处理变换后的图像。这种变换可以模拟从不同角度观察物体的效果,例如在文档扫描应用中,将倾斜的文档矫正为矩形。

七、图像去噪和图像修复中的应用

在图像去噪和图像修复中,有时也会使用双线性插值作为一种简单的方法。对于含有噪声的图像,我们可以将噪声像素视为缺失信息,通过双线性插值使用周围像素的信息来填补。例如,在图像中存在一些小块缺失区域时,可以使用双线性插值进行简单的填充:

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;


typedef cv::Point3_<uint8_t> Pixel;


// 双线性插值算法
void bilinearInterpolation(Mat& src, Mat& dst, double sx, double sy) {
    int dst_rows = static_cast<int>(src.rows * sy);
    int dst_cols = static_cast<int>(src.cols * sx);
    dst = Mat::zeros(cv::Size(dst_cols, dst_rows), src.type());


    dst.forEach<Pixel>([&](Pixel &p, const int * position) -> void {
        int row = position[0];
        int col = position[1];


        double before_x = double(col + 0.5) / sx - 0.5f;
        double before_y = double(row + 0.5) / sy - 0.5;
        int top_y = static_cast<int>(before_y);
        int bottom_y = top_y + 1;
        int left_x = static_cast<int>(before_x);
        int right_x = left_x + 1;


        double u = before_x - left_x;
        double v = before_y - top_y;


        if ((top_y >= src.rows - 1) && (left_x >= src.cols - 1)) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k] = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k];
            }
        } else if (top_y >= src.rows - 1) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * u * src.at<Vec3b>(top_y, right_x)[k];
            }
        } else if (left_x >= src.cols - 1) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k];
            }
        } else {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * (u) * src.at<Vec3b>(top_y, right_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k]
                          + (u) * (v) * src.at<Vec3b>(bottom_y, right_x)[k];
            }
        }
    });
}


int main() {
    Mat src = imread(".../grass.jpg");
    imshow("src", src);


    Mat mask = Mat::zeros(src.size(), CV_8UC1);
    // 假设一个矩形区域为噪声或缺失区域
    rectangle(mask, Rect(100, 100, 200, 200), Scalar(255), -1);


    Mat dst;
    src.copyTo(dst, ~mask);


    bilinearInterpolation(dst, dst, 1.0, 1.0);


    imshow("Denoised", dst);


    waitKey(0);
    return 0;
}

在这个示例中,我们创建了一个掩膜 mask 来标记噪声或缺失区域,然后将该区域置零,使用双线性插值对该区域进行填充。然而,需要注意的是,双线性插值在图像修复中的效果可能不太理想,对于复杂的噪声或缺失区域,更高级的图像修复算法,如基于纹理合成、基于深度学习的修复算法,通常能取得更好的效果。

八、机器学习中的应用

在机器学习中,尤其是在计算机视觉的深度学习任务中,双线性插值也有其应用。例如,在图像预处理阶段,当调整输入图像的大小以适应神经网络的输入大小时,双线性插值可以作为一种图像缩放的方法。许多深度学习框架(如 TensorFlow、PyTorch)都内置了双线性插值的功能,并且在一些情况下,它比其他插值方法更受欢迎,因为它在计算成本和效果之间取得了较好的平衡。

当训练图像数据时,不同的图像可能具有不同的尺寸,为了将它们输入到一个统一尺寸的神经网络中,我们可以使用双线性插值对图像进行缩放。同时,在一些生成对抗网络(GANs)中,双线性插值可以用于图像生成过程中的上采样操作,帮助生成更高分辨率的图像。

九、图像质量评估和优化

双线性插值对图像质量有一定的影响,因此在图像质量评估中也需要考虑其影响。例如,当使用峰值信噪比(PSNR)或结构相似性指数(SSIM)等指标评估图像质量时,使用不同插值方法得到的结果可能会有所不同。通过对双线性插值的效果进行评估,可以为选择合适的插值方法提供依据。

在图像优化中,我们可以根据双线性插值的结果,结合其他图像处理技术,如锐化滤波,来提高缩放后图像的质量。以下是一个简单的示例,在双线性插值后使用锐化滤波器:

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;


typedef cv::Point3_<uint8_t> Pixel;


// 双线性插值算法
void bilinearInterpolation(Mat& src, Mat& dst, double sx, double sy) {
    int dst_rows = static_cast<int>(src.rows * sy);
    int dst_cols = static_cast<int>(src.cols * sx);
    dst = Mat::zeros(cv::Size(dst_cols, dst_rows), src.type());


    dst.forEach<Pixel>([&](Pixel &p, const int * position) -> void {
        int row = position[0];
        int col = position[1];


        double before_x = double(col + 0.5) / sx - 0.5f;
        double before_y = double(row + 0.5) / sy - 0.5;
        int top_y = static_cast<int>(before_y);
        int bottom_y = top_y + 1;
        int left_x = static_cast<int>(before_x);
        int right_x = left_x + 1;


        double u = before_x - left_x;
        double v = before_y - top_y;


        if ((top_y >= src.rows - 1) && (left_x >= src.cols - 1)) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k] = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k];
            }
        } else if (top_y >= src.rows - 1) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * u * src.at<Vec3b>(top_y, right_x)[k];
            }
        } else if (left_x >= src.cols - 1) {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k];
            }
        } else {
            for (size_t k = 0; k < src.channels(); k++) {
                dst.at<Vec3b>(row, col)[k]
                        = (1. - u) * (1. - v) * src.at<Vec3b>(top_y, left_x)[k]
                          + (1. - v) * (u) * src.at<Vec3b>(top_y, right_x)[k]
                          + (v) * (1. - u) * src.at<Vec3b>(bottom_y, left_x)[k]
                          + (u) * (v) * src.at<Vec3b>(bottom_y, right_x)[k];
            }
        }
    });
}


int main() {
    Mat src = imread(".../grass.jpg");
    imshow("src", src);


    double sx = 1.5;
    double sy = 1.5;


    Mat dst;
    bilinearInterpolation(src,dst, sx, sy);


    Mat sharpened;
    Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0,
                               -1, 5, -1,
                               0, -1, 0);
    filter2D(dst, sharpened, -1, kernel);


    imshow("Sharpened", sharpened);


    waitKey(0);
    return 0;
}

在这个示例中,我们在双线性插值后的图像上使用了一个锐化滤波器(拉普拉斯算子),以增强图像的边缘和细节,改善图像的视觉效果。


http://www.kler.cn/a/502550.html

相关文章:

  • Unity自带的真车模拟系统,速度不够大r时如何以匀速上桥
  • 贪心算法笔记
  • 【Rust】结构体定义域实例化
  • Git撤销指定commit并更新远端仓库
  • SQL分类与数据类型整理
  • Spring配置文件中:密码明文改为密文处理方式(通用方法)
  • 【数据库】Mysql精简回顾复习
  • 【人工智能】自然语言生成的前沿探索:利用GPT-2和BERT实现自动文本生成与完形填空
  • python-leetcode-长度最小的子数组
  • C#版 软件开发6大原则与23种设计模式
  • 【理论】测试框架体系TDD、BDD、ATDD、MBT、DDT介绍
  • 2025年华为OD上机考试真题(Java)——整数对最小和
  • vulnhub靶场【IA系列】之Keyring
  • 关于Java状态模式的面试题及其答案
  • 全网首发:编译libssh,产生类似undefined reference to `EVP_aes_256_ctr@OPENSSL_1_1_0‘的大量错误
  • 基于深度学习的滑块验证破解方法及模型训练过程
  • 算法日记2:洛谷p3853路标设置(二分答案)
  • 女性机器人有市场吗
  • Scaling Laws:通往更大模型的路径
  • Mysql常见知识点
  • Vulnhub DC-9靶机实战
  • 【深度学习】通俗理解偏差(Bias)与方差(Variance)
  • 野指针bug
  • 代码随想录day24 | leetcode 491.递增子序列 46.全排列 47.全排列 II
  • 【信息系统项目管理师】高分论文:论信息系统项目的采购管理(社会保险管理核心系统)
  • 第37周:咖啡豆识别 (Tensorflow实战第七周)