OpenCV实战——使用邻居访问扫描图像

news2024/9/23 7:23:23

OpenCV实战——使用邻居访问扫描图像

    • 0. 前言
    • 1. 图像锐化
    • 2. 邻居访问扫描图像
    • 3. 锐化滤波器
    • 4. 完整代码
    • 相关链接

0. 前言

在图像处理中,有时需要根据某个像素的相邻像素的值计算该像素位置的值。当这个邻域包括上一行和下一行的像素时,就需要同时扫描图像的多行像素,本节中,我们将介绍如何通过邻居访问扫描图像。

1. 图像锐化

为了说明邻域扫描方法,我们将应用一个基于拉普拉斯算子的处理函数来锐化图像。在图像处理,如果从图像中减去它的拉普拉斯算子,图像边缘会被放大,从而得到更清晰的图像。锐化值计算如下:

sharpened_pixel= 5*current-left-right-up-down;

其中,left 是紧接在当前像素左侧的像素,up 是上一行的邻居像素,依此类推。接下来,我们介绍如何实现锐化函数。

2. 邻居访问扫描图像

(1) 我们将创建一个带有输入和输出图像的锐化函数,并不使用原地处理,即函数需要提供输出图像:

void sharpen(const cv::Mat &image, cv::Mat &result)

(2) 分配输出结果图像,通过 channels() 函数获取输入图像的通道数:

result.create(image.size(), image.type());
int nchannels= image.channels();

(3) 接下来,我们循环处理图像中的每一行。图像扫描使用三个指针完成,一个指向当前行,一个指向前一行,另一个指向下一行。此外,由于每个像素计算都需要访问其邻居,因此无法计算图像第一行和最后一行的像素以及第一列和最后一列的像素的值:

for (int j=1; j<image.rows-1; j++) {
    const uchar* previous = image.ptr<const uchar>(j-1);
    const uchar* current = image.ptr<const uchar>(j);
    const uchar* next = image.ptr<const uchar>(j+1);
    uchar* output = result.ptr<uchar>(j);
    for (int i=channels; i<(image.cols-1)*nchannels; i++){
        *output++ = cv::saturate_cast<uchar>(
                    5*current[i]-current[i-nchannels]-current[i+nchannels]-previous[i]-next[i]);
    }
}

以上代码可以在灰度和彩色图像上工作。如果我们将此函数应用于测试彩色图像,可以得到以下结果:

图像锐化

为了访问前一行和下一行的相邻像素,必须定义附加指针,然后在扫描循环内访问这些行中的像素。
在计算输出像素值时,会根据运算结果调用 cv::saturate_cast 模板函数,这是因为应用于像素的数学表达式可能会导致超出允许像素值范围的结果(即低于 0 或高于 255)。解决方案是将像素值重置到 [0, 255] 范围内,将负值改为 0 并将超过 255 的值改为 255,这正是 cv::saturate_cast<uchar> 函数的作用。此外,如果输入参数是浮点数,则结果将四舍五入为最接近的整数。我们也可以将此函数与其他类型一起使用,以确保结果保持在此类型定义的范围内。
由于其邻域未完全定义而无法处理的边界像素需要单独处理。在这里,我们简单的将它们设为 0;在复杂情况下,可以对这些像素执行特殊计算,但在大多数情况下,花时间处理这些极少数像素是没有意义的。我们可以使用两种特殊的方法将这些边缘像素设置为 0,可以使用 rowcol,它们返回一个特殊的 cv::Mat 实例,该实例由参数中指定的单行感兴趣区域 (region of interest, ROI) (或单列 ROI) 组成。这里不需要进行复制,因为如果修改这个一维矩阵的元素,它们在原始图像中也会被修改,我们可以通过调用 setTo() 方法实现,setTo() 方法可以为矩阵的所有元素分配值:

result.row(0).setTo(cv::Scalar(0));

以上代码可以将值 0 分配给结果图像第一行的所有像素。在三通道彩色图像的情况下,需要使用 cv::Scalar(a,b,c) 指定要分配给像素的每个通道的三个值。

3. 锐化滤波器

当对像素邻域进行计算时,通常用核矩阵表示它,核描述了如何组合计算中涉及的像素以获得所需的结果。本节中使用的锐化滤波器核如下:

[ 0 − 1 0 − 1 5 − 1 0 − 1 0 ] \begin{bmatrix} 0 & -1 & 0\\ -1 & 5 & -1 \\ 0 & -1 & 0 \end{bmatrix} 010151010

通常,当前像素对应于核的中心,核的每个单元格中的值表示乘以相应像素的因子。然后将计算所有乘法的总和得到核应用于像素的结果。核的大小对应于邻域的大小(此处为 3 x 3)。使用这种表示,可以看出,锐化滤波器的计算方法:当前像素的水平和垂直邻居乘以 -1,而当前像素乘以 5。将核应用于图像不仅仅是一种方便的表示,同时也是信号处理中卷积概念的基础,核定义了一个应用于图像的滤波器。
由于滤波是图像处理中的一个常见操作,OpenCV 定义了一个特殊的函数来执行这个任务——cv::filter2D 函数。要使用此函数,只需要使用矩阵的形式定义一个核,然后使用图像和核调用该函数,并返回滤波后的图像。因此,使用 cv::filter2D 函数,可以很容易重新定义锐化函数:

void sharpen2D(const cv::Mat &image, cv::Mat &result) {
    // 创建3x3核,所有元素初始化为0
    cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
    // 为核赋值
    kernel.at<float>(1,1) = 5.0;
    kernel.at<float>(0,1) = -1.0;
    kernel.at<float>(2,1) = -1.0;
    kernel.at<float>(1,0) = -1.0;
    kernel.at<float>(1,2) = -1.0;
    // 图像滤波
    cv::filter2D(image, result, image.depth(), kernel);
}

使用此函数可以得到与上一小节中代码完全相同的结果(并且具有相同的效率),如果输入彩色图像,则相同的内核将应用于所有三个通道。在使用较大尺寸的核时,cv::filter2D 函数更加高效。

4. 完整代码

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

void sharpen(const cv::Mat &image, cv::Mat &result) {
    result.create(image.size(), image.type());
    int nchannels = image.channels();
    for (int j=1; j<image.rows-1; j++) { // 循环除第一行和最后一行外的所有行
        const uchar* previous = image.ptr<const uchar>(j-1);
        const uchar* current = image.ptr<const uchar>(j);
        const uchar* next = image.ptr<const uchar>(j+1);
        uchar* output = result.ptr<uchar>(j);
        for (int i=nchannels; i<(image.cols-1)*nchannels; i++) {
            *output++ = cv::saturate_cast<uchar>(5*current[i]-current[i-nchannels]-current[i+nchannels]-previous[i]-next[i]);
        }
    }
    // 将未处理的像素置0
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows-1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols-1).setTo(cv::Scalar(0));
}

// 使用迭代器,该函数的输入图像必须为灰度图像
void sharpenIterator(const cv::Mat &image, cv::Mat &result) {
    // 输入图像必须为灰度图像
    CV_Assert(image.type()==CV_8UC1);
    // 初始化迭代器
    cv::Mat_<uchar>::const_iterator it = image.begin<uchar>() + image.cols;
    cv::Mat_<uchar>::const_iterator itend = image.end<uchar>() - image.cols;
    cv::Mat_<uchar>::const_iterator itup = image.begin<uchar>();
    cv::Mat_<uchar>::const_iterator itdown = image.begin<uchar>() + 2*image.cols;
    // 设置输出图像和迭代器
    result.create(image.size(), image.type());
    cv::Mat_<uchar>::iterator itout = result.begin<uchar>() + result.cols;
    for (; it!=itend; ++it,++itout,++itup,++itdown) {
        *itout = cv::saturate_cast<uchar>(*it * 5 - *(it-1) - *(it+1) - *itup - *itdown);
    }
    // 将未处理的像素置0
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows-1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols-1).setTo(cv::Scalar(0));
}

// 使用核
void sharpen2D(const cv::Mat &image, cv::Mat &result) {
    // 构造3x3核,并将所有元素初始化为0
    cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
    // 为核元素赋值
    kernel.at<float>(1, 1) = 5.0;
    kernel.at<float>(0, 1) = -1.0;
    kernel.at<float>(2, 1) = -1.0;
    kernel.at<float>(1, 0) = -1.0;
    kernel.at<float>(1, 2) = -1.0;
    // 图像滤波
    cv::filter2D(image, result, image.depth(), kernel);
}

int main() {
    cv::Mat image = cv::imread("1.png");
    if (!image.data) return 0;
    cv::Mat result;
    double time = static_cast<double>(cv::getTickCount());
    sharpen(image, result);
    time = (static_cast<double>(cv::getTickCount())-time) / cv::getTickFrequency();
    std::cout << "time = " << time << "s" << std::endl;
    cv::namedWindow("Image");
    cv::imshow("Image", result);
    // 使用灰度模式打开图像
    image = cv::imread("1.png", 0);
    time = static_cast<double>(cv::getTickCount());
    sharpenIterator(image, result);
    time = (static_cast<double>(cv::getTickCount())-time) / cv::getTickFrequency();
    std::cout << "time gray level = " << time << "s" << std::endl;
    cv::namedWindow("Sharpened Image");
    cv::imshow("Sharpened Image", result);
    // 测试sharpen2D
    image = cv::imread("1.png");
    time = static_cast<double>(cv::getTickCount());
    sharpen2D(image, result);
    time = (static_cast<double>(cv::getTickCount())-time) / cv::getTickFrequency();
    std::cout << "time sharpen 2D = " << time << "s" << std::endl;
    cv::namedWindow("Image Filter 2D");
    cv::imshow("Image Filter 2D", result);
    cv::waitKey();
    return 0;
}

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作

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

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

相关文章

JavaScript高级 浏览器的渲染原理与JavaScript代码执行原理

浏览器的渲染原理1. 网页的解析过程2. 浏览器内核1. V8引擎3. 浏览器渲染过程1. HTML解析2. 生成CSS规则3. 构建Render Tree4. 布局&#xff08;layout&#xff09;5. 绘制&#xff08;Paint&#xff09;4. 回流和重绘解析1. 回流2. 重绘3. web 性能优化4. 合成和性能优化5. 浏…

剑指offer----C语言版----第六天

目录 1. 用栈实现队列 1.1 题目描述 1.2 栈和队列的基础知识 1.3 思路分析 2. 扩展题目——用队列实现栈 2.1 题目描述 2.2 思路分析 1. 用栈实现队列 原题链接&#xff1a; 剑指 Offer 09. 用两个栈实现队列 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/…

数据结构集合框架与大O渐进表示法

作者&#xff1a;爱塔居的博客_CSDN博客-JavaSE领域博主 专栏&#xff1a;数据结构 作者简介&#xff1a;大三学生&#xff0c;希望跟大家一起进步&#xff01;&#x1f339; 博客简介&#xff1a;了解数据结构&#xff0c;学习一些关于数据结构的前置知识 文章目录 目录 文章目…

【论文翻译】Deep Learning for Multi-view Stereo via Plane Sweep: A Survey(2021)

一、论文简述 1. 第一作者&#xff1a;Qingtian Zhu 2. 发表年份&#xff1a;2021 3. 发表期刊&#xff1a;CVPR 4. 关键词&#xff1a;MVS、深度学习、综述 5. 核心思想&#xff1a;读到的第一篇深度MVS的综述&#xff0c;总结的很好&#xff0c;内容涵盖了2021年前的研究…

遗传算法python进阶理解+论文复现(纯干货,附前人总结引路)

遗传算法python进阶理解论文复现&#xff08;纯干货&#xff0c;附前人总结引路&#xff09;一、简介和相关概念遗传算法简介相关概念介绍二、与其他智能优化算法的比较蚁群算法粒子群优化算法人工神经网络算法模拟退火算法鱼群算法三、必学知识&#xff08;站在前人的肩膀上&a…

1214. 波动数列(推公式 + DP)

题目如下&#xff1a; 思路 or 题解&#xff1a; 我们可以设&#xff1a; 第一个数为 xxx d {a, -b} 那后续的数为&#xff1a;xd1x d_1xd1​ , xd1d)2x d_1 d_)2xd1​d)​2 … … xd1d2......dn−1x d_1 d_2 ... ... d_{n - 1}xd1​d2​......dn−1​ 根据题意和上面…

(Java高级教程)第二章Java多线程常见面试题-第三节:线程安全集合类和死锁

文章目录一&#xff1a;线程安全集合类&#xff08;1&#xff09;多线程环境下使用ArrayList&#xff08;2&#xff09;多线程环境使用队列&#xff08;3&#xff09;多线程使用哈希表二&#xff1a;死锁&#xff08;1&#xff09;概念&#xff08;2&#xff09;死锁产生的四个…

(小程序)会议OA项目-首页

目录 一、FIex布局简介 1.什么是flex布局&#xff1f; 2.flex属性 学习地址&#xff1a; 3.flex弹性布局演示 ① 容器的属性 ⑴ flex-direction属性 ⑵ flex-wrap属性 ⑶ flex-flow ⑷ justify-content属性 ⑸ align-items属性 ⑹ align-content属性 二、轮播图组件m…

微信记录怎么恢复?恢复已删除微信历史记录的4种方式

恢复已删除微信历史记录的4种方式 如何在有/没有备份的情况下在 iPhone 和 Android 上恢复旧的或已删除的微信历史记录&#xff0c;如聊天对话、语音消息、照片、图片和视频剪辑&#xff1f;参考本指南&#xff0c;祝您成功恢复微信数据。 关于微信数据恢复 “说真的&#xf…

容器化技术Docker与任务编排

Docker容器化 Docker简介 传统的Java项目部署需要自己进行打包&#xff0c;redis&#xff0c;nignx等中间件需要安装以及进行很多配置&#xff0c;稍微繁琐&#xff0c;而Docker使用了容器化的技术把这一过程封装为一条指令解决&#xff0c;而这取决于它的架构设计&#xff0c…

数值优化之函数高阶信息

本文ppt来自深蓝学院《机器人中的数值优化》 目录 1 函数高阶信息的介绍 2 函数高阶信息的计算 1 函数高阶信息的介绍 hessian矩阵是对称矩阵&#xff0c;最后一个公式是函数关于0的泰勒展开 负梯度是函数下降的最快方向 注意区分Hessian矩阵与Jacobian矩阵&#xff0c;Hess…

9_4、Java基本语法之System、Math、BigInteger与BigDecimal类的使用

一、System类的使用 1、System类代表系统&#xff0c;系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。 由于该类的构造器是private的&#xff0c;所以无法创建该类的对象&#xff0c;也就是无法实 例化该类。其内部的成员变量和成员方法都是static的&am…

【算法题解】7. 反转链表

文章目录题目解法一&#xff1a;迭代解题思路代码实现复杂度分析解法二&#xff1a;递归解题思路代码实现复杂度分析题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。来自&#xff1a;leetcode 解法一&#xff1a;迭代 解题思路 使用…

广州车展智能卷王:集度ROBO-01的取与舍

作者 | 德新 编辑 | 王博2022年的最后两天&#xff0c;今年最后一个A级车展在广州开幕。由于种种原因&#xff0c;不少车企匆忙备战或者干脆缺席&#xff0c;这届展会不如往届热闹。但也有憋了大招的选手&#xff0c;比如集度。 12月30日上午&#xff0c;集度在一场非常短的发布…

11-内部类and 12-集合初步

文章目录11-内部类链接外部类使用.this 和.new匿名内部类12-集合初步思考1&#xff0c;List<Apple> apples new ArrayList<>();思考2&#xff0c;如何初始化一个collection思考3&#xff0c;Pet关键字思考4&#xff0c;关于ListIterator这个双向迭代器思考5&#…

保护性暂停设计模式

目录 保护性暂停设计模式 获取结果 产生结果 总代码实现 测试 增加超时效果的Guarded suspension get(long timeout) 测试 保护性暂停设计模式 Guarded Suspension 即 保护性暂停; 是一种等待唤醒机制的一种规范 ,也可以理解为使用中设计模式,Java的API很多都按照保护性…

【免费开放源码】审批类小程序项目实战(活动审批端)

第一节&#xff1a;什么构成了微信小程序、创建一个自己的小程序 第二节&#xff1a;微信开发者工具使用教程 第三节&#xff1a;深入了解并掌握小程序核心组件 第四节&#xff1a;初始化云函数和数据库 第五节&#xff1a;云数据库的增删改查 第六节&#xff1a;项目大纲以及制…

系分 - 面向对象的方法【概念】

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - &#xff08;概念&#xff09;面向对象的方法 面向对象的方法&#xff08;OO&#xff0c;Object-Oriented&#xff09;是一种基于对象模型的程序设计方法&#xff0c;包括面向对象的分析&#xff08;OOA&a…

【iOS】内存管理

文章目录前言理解引用计数引用计数原理属性存取方法中的内存管理自动释放池保留环以ARC简化引用计数使用ARC时必须遵守的命名规则变量的内存管理语义ARC如何清理实例变量覆写内存管理的方法在dealloc方法中只释放应用并解除监听前言 内存管理&#xff1a; 在Objective-C这样的…

Windows平台下的内存泄漏检测

Windows平台下的内存泄漏检测一、使用_CrtDumpMemoryLeaks定位内存泄露添加对应的头文件转储内存泄漏信息程序任意点退出指定调试信息输出二、定位具体内存泄露位置内存快照转储内存快照比较内存快照完整例子三、使用WinDbg定位获取堆信息查看指定堆的使用情况获取地址信息获取…