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

devtools/2025/1/14 20:12:45/

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

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

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

#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.ppmy.cn/devtools/150497.html

相关文章

TypeScript 爬虫项目实战:抓取豆瓣电影 Top 250(TypeScript简单应用)

项目介绍 通过 TypeScript 实现一个简单的爬虫程序&#xff0c;从豆瓣电影 Top 250 页面抓取电影的标题和评论信息&#xff0c;并将数据存储到本地 JSON 文件中。该项目使用了 superagent 和 cheerio 两个核心工具&#xff1a; superagent&#xff1a;一个轻量的 HTTP 请求库…

性能测试工具的原理与架构解析

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在软件开发与运维领域&#xff0c;性能测试是确保系统稳定、高效运行的关键环节。性能测试工具作为实现这一目标的重要工具&#xff0c;通过模拟真实用户行为和负载…

vue时间格式转换

在 Vue 前端中&#xff0c;如果你想将日期格式改为 "yyyy-MM-dd HH:mm:ss"&#xff0c;可以使用 JavaScript 的 Date 对象以及常用的日期处理库&#xff08;比如 moment.js 或 dayjs&#xff09;来实现格式化。以下是两种常见的解决方案&#xff1a; 方法 1: 使用 d…

关于使用FastGPT 摸索的QA

近期在通过fastGPT&#xff0c;创建一些基于特定业务场景的、相对复杂的Agent智能体应用。 工作流在AI模型的基础上&#xff0c;可以定义业务逻辑&#xff0c;满足输出对话之外的需求。 在最近3个月来的摸索和实践中&#xff0c;一些基于经验的小问题点&#xff08;自己也常常…

python 生成24bit音频数据实例解析

一 概念 24 bit 是指音频文件的 采样深度 &#xff08;bit depth&#xff09;。 它代表了每个采样点的数据精度&#xff0c;也就是音频每个样本所使用的比特数。 24 bit 的采样深度相较于 16 bit 提供了更高的动态范围和更精确的音频信息表示。 动态范围&#xff1a;24 bit 的…

vue.js路由重定向

在Vue.js中&#xff0c;路由重定向是指当用户访问某个特定路由时&#xff0c;自动将其重定向到另一个路由。这在应用程序中非常有用&#xff0c;可以将用户引导到特定的页面或重定向到其他相关页面。 在Vue.js中&#xff0c;我们可以通过routes数组中的redirect属性来实现路由…

STM32 FreeRTOS移植

目录 FreeRTOS源码结构介绍 获取源码 1、 官网下载 2、 Github下载 源码结构介绍 源码整体结构 FreeRTOS文件夹结构 Source文件夹结构如下 portable文件夹结构 RVDS文件夹 MemMang文件夹 FreeRTOS在基于寄存器项目中移植步骤 目录添加源码文件 工程添加源码文件 …

LLM 大语言模型学习记录

文章目录 1. 写在最前面2. 让 gpt-4o-mini 解释 LLM 的原理3. 让 gpt-4o-mini 解释 LLM 的使用场景4. 让 gpt-4o-mini 解释 LLM 的局限性5. 碎碎念 1. 写在最前面 大语言模型&#xff08;LLM&#xff09;似乎已经成为 AI 领域的宠儿&#xff0c;不少公司争相推出自研的大模型。…