OpenCV 笔记(22):图像的缩放——最近邻插值、双线性插值算法

1. 图像缩放

1.1 简介

图像缩放是指通过增加或减少像素来改变图像尺寸的过程,是图像处理中常见的操作。图像缩放会涉及效率和图像质量之间的权衡。

图像放大(也称为上采样插值)的主要目的是放大原图像,以便在更高分辨率的显示设备上显示。但是,放大图像并不能带来更多信息,因此图像质量会不可避免地受到影响。

图像缩小(也称为下采样)的主要目的是减小图像尺寸,以便更有效地存储或传输。缩小图像可以保留更多信息,但图像细节会丢失。

1.2 图像缩放方法分类

  • 空间域方法:直接在图像像素空间进行操作。常见的空间域缩放方法包括:

    • 最近邻插值:简单快速,但图像质量较差。

    • 双线性插值:图像质量比最近邻插值好,但计算量更大。

    • 立方插值:图像质量比双线性插值好,但计算量更大。

常见空间域缩放方法的比较:

方法优点缺点
最近邻插值简单快速容易产生锯齿
双线性插值平滑图像可能导致细节模糊
立方插值效果更好计算量较大
  • 频域方法:将图像转换为频域,然后在频域进行操作。常见的频域缩放方法包括:

    • 傅里叶插值:将图像转换为傅里叶频谱,然后根据缩放比例调整频谱大小,再将逆傅里叶变换回图像空间。傅里叶插值可以保持图像边缘锐度。图像质量较高,但计算量较大。

    • Lanczos 插值:一种改进的傅里叶插值算法,通过使用低通滤波器来消除频谱中的混叠现象,平衡了速度和质量,是常用频域算法之一。

2.  插值算法

图像插值算法是指在已知像素值的基础上,估计未知像素值的数学方法。OpenCV 提供了多种插值算法,用于图像缩放、旋转、仿射变换等操作。

在数学的数值分析领域中,内插,或称插值(英语:Interpolation),是一种通过已知的、离散的数据点,在范围内推求新数据点的过程或方法。

2.1 最近邻插值(Nearest Neighbor Interpolation)

最近邻插值通过找到目标像素在原图像中最近的像素值来赋值给目标像素。具体来说,根据原图像和目标图像的尺寸,计算缩放的比例,然后根据缩放比例计算目标像素所依据的原像素,并将该值赋给目标像素。

其中, 、 表示原图像中的坐标, 、 表示目标图像中的坐标,scale 表示放缩倍数。

最近邻插值的优点:

  • 算法简单,计算量小,速度快。

  • 不会产生新的像素值,保持原始图像的灰度值。

最近邻插值的缺点:

  • 容易产生锯齿现象,图像质量较低。

1a3a02e3e496e496f585dfb43c1cf0e0.jpeg
最近邻插值.png

下面的代码,展示了如何实现最近邻插值算法

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"using namespace std;
using namespace cv;//最近邻插值算法
void nearestNeighbor(cv::Mat& src, cv::Mat& dst, float sx, float sy)
{// 由 scale 计算输出图像的尺寸(四舍五入)int dst_cols = round(src.cols * sx);int dst_rows = round(src.rows * sy);dst = cv::Mat(dst_rows,dst_cols,src.type());for (int i = 0; i < dst.rows; i++){for (int j = 0; j < dst.cols; j++){if (src.channels() == 1) {// 插值计算,输出图像的像素点由原图像对应的最近的像素点得到(四舍五入)int i_index = round(i / sy);int j_index = round(j / sx);if (i_index > src.rows - 1) i_index = src.rows - 1;//防止越界if (j_index > src.cols - 1) j_index = src.cols - 1;//防止越界dst.at<uchar>(i, j) = src.at<uchar>(i_index, j_index);} else {// 插值计算,输出图像的像素点由原图像对应的最近的像素点得到(四舍五入)int i_index = round(i / sy);int j_index = round(j / sx);if (i_index > src.rows - 1) i_index = src.rows - 1;//防止越界if (j_index > src.cols - 1) j_index = src.cols - 1;//防止越界dst.at<cv::Vec3b>(i, j)[0] = src.at<cv::Vec3b>(i_index, j_index)[0];dst.at<cv::Vec3b>(i, j)[1] = src.at<cv::Vec3b>(i_index, j_index)[1];dst.at<cv::Vec3b>(i, j)[2] = src.at<cv::Vec3b>(i_index, j_index)[2];}}}
}int main()
{Mat src = imread(".../grass.jpg");imshow("src", src);Mat dst;nearestNeighbor(src, dst,1.5, 1.5);imshow("dst", dst);waitKey(0);return 0;
}
2f3f3820f526f629b5b60c2ce57317fd.jpeg
原图和最近邻插值实现的缩放.png

下面的代码,通过 Mat 的 forEach() 结合 C++11 lambda 表达式,实现对 Mat 对象快速像素遍历,进而重构了最近邻插值算法。

typedef cv::Point3_<uint8_t> Pixel;//最近邻插值算法
void nearestNeighbor(cv::Mat& src, cv::Mat& dst, float sx, float sy)
{// 由 scale 计算输出图像的尺寸(四舍五入)int dst_cols = round(src.cols * sx);int dst_rows = round(src.rows * sy);dst = cv::Mat(dst_rows,dst_cols,src.type());dst.forEach<Pixel>([&](Pixel &p, const int * position) -> void {int row = position[0];int col = position[1];if (src.channels() == 1) {int i_index = round(row / sy);int j_index = round(col / sx);dst.at<uchar>(row, col) = src.at<uchar>(i_index, j_index);} else {int i_index = round(row/ sy);int j_index = round(col / sx);dst.at<cv::Vec3b>(row, col)[0] = src.at<cv::Vec3b>(i_index, j_index)[0];dst.at<cv::Vec3b>(row, col)[1] = src.at<cv::Vec3b>(i_index, j_index)[1];dst.at<cv::Vec3b>(row, col)[2] = src.at<cv::Vec3b>(i_index, j_index)[2];}});
}

2.2 双线性插值(Bilinear Interpolation)

先介绍一下线性插值,线性插值是一种估计两个已知数据点之间的值的方法。

5373ee69bdfa3e4ee9675df57b9c4113.jpeg
线性插值.png

假设我们已知坐标 (,) 与 (,),要得到 [,] 区间内某一位置 x 在直线上的值。由上图可得:

由于 x 已知,则 y:

所以,这是在 x 方向上进行了一次线性插值。

双线性插值是对 x 方向和 y 方向分别进行插值,它根据原始图像中四个相邻像素的值来估计新位置处像素的值。它是一维线性插值的扩展。

e9fd8707f2dd9a3279ba366882e6f0ff.jpeg
双线性插值.png

在上图中,假设已知、、、四个点,我们要估计由这四个点组成的矩形内的任意点(x,y)处像素值 f(x,y) 。

  • 对沿 y 轴的两对点 、在 x 方向进行线性插值:

  • 对沿 y 轴的两对点 、在 x 方向进行线性插值:

  • 对沿 x 轴的两对点 、在 y 方向进行线性插值:

此时,一共执行了三次线性插值,双线性插值只是对 x、y 方向进行插值,而不是进行两次插值。

双线性插值用于根据原始图像中的已知值来估计调整大小的图像中像素的强度或颜色值。与最近邻插值相比,这种方法可以产生更平滑的结果,后者可能会导致可见的伪影或锯齿状边缘。

下面的代码,展示了如何实现双线性插值算法。

#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);double sx = 1.5;double sy = 1.5;Mat dst;bilinearInterpolation(src,dst, sx, sy);imshow("dst", dst);waitKey(0);return 0;
}
cf8fa61c541bb9d2a7acfe0d30c7375b.jpeg
原图和双线性插值实现的缩放.png

3.  总结

图像缩放是图像处理中一项重要的技术,具有广泛的应用场景。

本文介绍了两种比较简单的插值算法:最近邻插值、双线性插值。最近邻插值适合于需要保持图像原始灰度值或边缘清晰度的场景。双线性插值适合于需要平滑图像的场景。如果需要更高的图像质量,可以考虑使用其他插值算法,例如立方插值或 Lanczos 插值,后续的文章也会介绍它们。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/257743.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot集成elasticsearch

一、依赖下载 创建好一个springboot项目&#xff0c;需要集成es&#xff1a; 因为springboot默认集成了es&#xff0c;但是版本号需要与本地或者服务器es的版本号一致&#xff0c;我本地es版本是7.14.0&#xff0c;所以需要在<properties></properties>中指定es版…

插值(一)——多项式插值(C++)

插值 插值的作用是可以将原本比较难计算的函数转换为误差在一定范围内的多项式&#xff0c;比如在单片机中直接计算 x 、 log ⁡ 2 x \sqrt{x}、\log_2x x ​、log2​x之类的函数是比较麻烦的&#xff0c;但是使用插值的方法就可以将其转换为误差可控的只有乘法和加减法的多项…

【机器学习案例4】为机器学习算法编码分类数据【含源码】

目录 编码分类数据 序数编码 标签编码 一次性编码 目标编码 目标编码的优点 目标编码的缺点 在现实生活中,收集的原始数据很少采用我们可以直接用于机器学习模型的格式,即数值型数据。因此,需要进行一些预处理,以便以正确的格式呈现数据、选择信息丰富的数据或降低其…

VitePress-12-markdown中使用vue的语法

前言 VitePress 中&#xff0c;markdown文档最终都会转换成为 html文件&#xff0c;我们在访问的时候&#xff0c;也是直接访问的 xxx.html 文件。而且&#xff0c;markdown文档会被作为 [vue单文件] 进行处理&#xff0c;因此&#xff0c;我们我们可以在文档中使用 vue 语法&…

C++ new 和 malloc 的区别?

相关系列文章 C new 和 malloc 的区别&#xff1f; C内存分配策略​​​​​​​ 目录 1.引言 2.区别 2.1.申请的内存分配区域 2.2.类型安全和自动大小计算 2.3.构造函数和析构函数的调用 2.4.异常处理 2.5.配对简便性 2.6.new 的重载 2.7.关键字和操作符 3.总结 1.引…

WebSocket原理详解

目录 1.引言 1.1.使用HTTP不断轮询 1.2.长轮询 2.websocket 2.1.概述 2.2.websocket建立过程 2.3.抓包分析 2.4.websocket的消息格式 3.使用场景 4.总结 1.引言 平时我们打开网页&#xff0c;比如购物网站某宝。都是点一下列表商品&#xff0c;跳转一下网页就到了商品…

OpenGL-ES 学习(4)---- OpenGL-ES 坐标体系

坐标体系 我们知道 OpenGL -ES 坐标系中每个顶点的 x&#xff0c;y&#xff0c;z 坐标都应该在 -1.0 到 1.0 之间&#xff0c;超出这个坐标范围的顶点都将不可见。 将一个物体&#xff08;图像&#xff09;渲染到屏幕上&#xff0c;通常经过将物体坐标转换为标准化设备坐标&am…

高德地图上绘制热力图的方法

百度地图和高德地图的JavaScript API都提供了热力图的绘制方法&#xff0c;都是将热力图作为新的图层&#xff0c;叠加到地图上。但是百度地图的经纬度体系与我们的经纬度存在偏差&#xff0c;高德的与我们相符&#xff0c;应当使用高德地图JavaScript API。 因为是JavaScript…

Elasticsearch:特定领域的生成式 AI - 预训练、微调和 RAG

作者&#xff1a;来自 Elastic Steve Dodson 有多种策略可以将特定领域的知识添加到大型语言模型 (LLM) 中&#xff0c;并且作为积极研究领域的一部分&#xff0c;正在研究更多方法。 对特定领域数据集进行预训练和微调等方法使 LLMs 能够推理并生成特定领域语言。 然而&#…

Mysql的安装、使用、优势与教程

一.安装 1.在小皮的设置界面检测3306端口&#xff0c;保障3306端口可用&#xff1b; 2、在小皮的首面界面&#xff0c;启动MySQL&#xff1b; 3、进行环境变量设置&#xff0c;找到MySQL的路径&#xff0c;进行复制&#xff1b; 4、在Windows的搜索栏内&#xff0c;输入“环境…

Linux 驱动开发基础知识——总线设备驱动模型(七)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

Linux——网络通信TCP通信常用的接口和tco服务demo

文章目录 TCP通信所需要的套接字socket()bind()listen()acceptconnect() 封装TCP socket TCP通信所需要的套接字 socket() socket()函数主要作用是返回一个描述符&#xff0c;他的作用就是打开一个网络通讯端口&#xff0c;返回的这个描述符其实就可以理解为一个文件描述符&a…

Vue核心基础5:数据监测、收集表单数据、过滤器

1 数据监测 【代码】 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>总结</title><scrip…

leetcode(二分查找)34.在排序数组中查找元素的第一个和最后一个位置(C++详细解释)DAY11

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计…

【最详解】如何进行点云的凹凸缺陷检测(opene3D)(完成度80%)

文章目录 前言实现思路想法1想法2想法3 补充实现想法1想法2代码 想法3代码 总结 前言 读前须知&#xff1a; 首先我们得确保你已经完全知晓相关的基本的数学知识&#xff0c;其中包括用最小二乘法拟合曲二次曲面&#xff0c;以及曲面的曲率详细求解。若还是没弄清楚&#xff0…

(17)Hive ——MR任务的map与reduce个数由什么决定?

一、MapTask的数量由什么决定&#xff1f; MapTask的数量由以下参数决定 文件个数文件大小blocksize 一般而言&#xff0c;对于每一个输入的文件会有一个map split&#xff0c;每一个分片会开启一个map任务&#xff0c;很容易导致小文件问题&#xff08;如果不进行小文件合并&…

Vue插槽

Vue插槽 一、插槽-默认插槽1.作用2.需求3.问题4.插槽的基本语法5.代码示例6.总结 二、插槽-后备内容&#xff08;默认值&#xff09;1.问题2.插槽的后备内容3.语法4.效果5.代码示例 三、插槽-具名插槽1.需求2.具名插槽语法3.v-slot的简写4.代码示例5.总结 四、作用域插槽1.插槽…

【AIGC】Stable Diffusion的ControlNet插件

ControlNet 介绍 ControlNet 插件是 Stable Diffusion 中的一个重要组件&#xff0c;用于提供对模型的控制和调整。以下是 ControlNet 插件的主要特点和功能&#xff1a; 模型控制&#xff1a; ControlNet 允许用户对 Stable Diffusion 中的模型进行精细的控制和调整。用户可以…

Linux_文件系统

假定外部存储设备为磁盘&#xff0c;文件如果没有被使用&#xff0c;那么它静静躺在磁盘上&#xff0c;如果它被使用&#xff0c;则文件将被加载进内存中。故此&#xff0c;可以将文件分为内存文件和磁盘文件。 内存文件 磁盘文件 软、硬链接 一.内存文件 1.1 c语言的文件接口 …

【web | CTF】BUUCTF [护网杯 2018] easy_tornado

天命&#xff1a;这题是框架性的漏洞&#xff0c;Python的web服务器框架&#xff0c;应该已经比较古老了 开局先看一下三个文件 简单阅读后会发现&#xff0c;这里存在文件包含漏洞&#xff0c;可以直接读取文件&#xff0c;但是有一个哈希值校验 一开始我以为是扫描文件后得到…