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.mzph.cn/news/685002.shtml

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

相关文章

RK3568笔记十五:触摸屏测试

若该文为原创文章&#xff0c;转载请注明原文出处。 使用正点原子的ATK-RK3568板子&#xff0c;一直在测试屏幕和视频&#xff0c;突然想到触摸屏测试&#xff0c;一直没有用过&#xff0c;原子给的demo跑的是QT系统&#xff0c;触摸功能是正常的&#xff0c;测试一下&#xf…

学习天机02

1.注入bean的写法 构造函数的注入 2.回答和评论 在做这个功能的时候需要理解一些概念&#xff0c;张三提出问题就是提问者&#xff0c;李四去回答张三的问题&#xff0c;李四就是回答者&#xff0c;王五去回答李四的评论&#xff0c;王五就是评论者。 在提供的InteractionRep…

MySQL免安装版安装教程

官网下载安装包 MySQL :: Download MySQL Community Server (Archived Versions) 选择mysql版本下载 安装配置MySQL 将下载完的Mysql安装包解压到指定目录 打开windos系统的cmd&#xff0c;以管理员身份运行 进入mysql文件夹中的bin目录 安装MySQL的服务mysqld --install 初…

幻兽帕鲁游戏联机的时候,显示“网络连接超时”怎么解决?

如果你在游戏联机的时候&#xff0c;显示“网络连接超时”&#xff0c;可以检查下&#xff1a; 1、前提是你已经按照教程部署成功 2、检查防火墙有没有忘记设置&#xff0c;协议是UDP&#xff08;只有TCP不行&#xff0c;一定要有UDP&#xff09;&#xff0c;端口是否填了8211&…

AI:128-基于机器学习的建筑物能源消耗预测

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

02 c++入门

目录 c关键字命名空间c输入&输出缺省参数函数重载引用内联函数auto关键字(c11)基于范围的for循环(c11)指针空值—nullptr(c11) 0. 本节知识点安排目的 c是在c的基础上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等…

【论文精读】DINO

摘要 基于对ViT在监督学习领域的表现质疑&#xff0c;探究自监督方法下的ViT是否具有更好的特征提取能力&#xff0c;进而发现&#xff1a; 自监督ViT特征包含场景布局、对象边界。这些信息可以在最后一自注意力模块中直接访问。自监督ViT特征结合最近邻分类器(k-NN)分类头中表…

java-8组合式异步编程

11.1 Future 接口 Future接口在Java5中被引人&#xff0c;设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算&#xff0c;返回一个执行运算结果的引用&#xff0c;当运算结束后&#xff0c;这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用…

【VSCode】使用笔记

目录 快捷键系列 相关插件 相关文档链接 快捷键系列 调出终端 ctrl 或者是ctrlJ 结束进程 ctrlc 注释 ctrlkc 取消注释 ctrlku 上下移动代码 alt方向键 多行光标ctrlalt方向键 快速跳过某个单词 ctrl方向键 相关插件 1.每次修改后&#xff0c;自动保存启动项目 相…

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱6(附带项目源码)

效果演示 文章目录 效果演示系列目录前言存储加载物品信息源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中&#xff0c;我们将探索如何用unity制作一个3D背包、库存、制作、快…

黄金交易策略(Nerve Nnife.mql4):移动止盈的设计

完整EA&#xff1a;Nerve Knife.ex4黄金交易策略_黄金趋势ea-CSDN博客 相较mt4的止盈止损&#xff0c;在ea上实现移动止盈&#xff0c;可以尽最大可能去获得更高收益。移动止盈的大体逻辑是&#xff1a;到达止盈点就开始追踪止盈&#xff0c;直到在最高盈利点回撤指定点数即平…

内网渗透Searchall敏感凭证信息搜索工具

一、开发背景 在实战中进入内网的时候&#xff0c;大家需要搜集一些敏感信息例如账号&#xff0c;密码甚至浏览器的账号密码。searchall完美解决了这个问题。所以我就结合自身的经验写了一款搜索敏感信息的利用工具。它可以搜索敏感信息&#xff0c;更快为你获取到有价值的信息…

【机器学习】数据清洗之识别重复点

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步…

武器级工具包 Immunity Canvas 7.26安装使用体验

介绍&#xff1a; Immunity Canvas工具包有集成化、自动化、简单化的特点&#xff0c;大幅降低了攻击门槛。该工具是Immunity公司的一款商业级漏洞利用和渗透测试工具&#xff0c;包含了480多个以上的漏洞利用&#xff0c;该工具工具本来并不开源&#xff0c;但是在2021年Immu…

函数递归与迭代附n的阶乘+顺序打印一个整数的每一位数+求第n个斐波那契数

1. 什么是递归&#xff1f; 递归其实是一种解决问题的方法&#xff0c;在C语言中&#xff0c;递归就是函数自己调用自己。 下面是一个最简单的C语言递归代码&#xff1a; #include <stdio.h> int main() {printf("hehe\n");main();//main函数中⼜调⽤了main函数…

基于Java (spring-boot)的房屋租赁管理系统

一、项目介绍 基于Java (spring-boot)的房屋租赁管理系统功能&#xff1a;登录、管理员、租客、公告信息管理、房屋信息管理、用户信息管理、租金信息管理、故障信息管理、房屋出租信息详情、个人信息、修改密码、等等等。 适用人群&#xff1a;适合小白、大学生、毕业设计、课…

linux优化空间完全卸载mysql——centos7.9

文章目录 ⭐前言⭐linux命令使用&#x1f496; 基础命令&#x1f496; 内存优化&#x1f496; 完全删除mysql ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享 linux优化空间&完全卸载mysql——centos7.9。 linux内存分配 在Linux中&#xff0c;内存分配是…

第7讲 SpringSecurity执行原理概述

SpringSecurity执行原理概述 spring security的简单原理&#xff1a; SpringSecurity有很多很多的拦截器&#xff0c;在执行流程里面主要有两个核心的拦截器 1&#xff0c;登陆验证拦截器AuthenticationProcessingFilter 2&#xff0c;资源管理拦截器AbstractSecurityInterc…

为什么电路要设计得这么复杂?

首先提出这个问题就很不容易啊&#xff0c;我们看两个精彩回答。 From 骄建&#xff1a; 假设我们回到第一个实用放大电路诞生之前&#xff1a; 某天你开始做一个CS单管放大器&#xff0c;电阻负载&#xff0c;可是有一大堆问题&#xff0c;电阻做的不准&#xff0c;温度对器…

AI:129-基于深度学习的极端天气事件预警

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…