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

opencv 学习: 05 像素操作

1.像素操作

图像处理归根到底是对图像像素的操作。

像素操作少不了对像素的遍历,而且要高效的遍历,因为像素数据量通常非常巨大。

以灰度图为例,其实一张图片就是一个矩阵,矩阵中的每个数字都表示一个像素。

灰度图,是一张由只包含明暗程度信息的像素组成的。通常,如果每个像素是unsigned 8-bit 格式,则 0 代表纯黑,255 代表纯白。

彩色图,是由红绿蓝三个色彩元素组成一个像素。每个像素中的各个色彩元素值,表示各自色彩的亮度。

这是由人的视觉系统决定的。

通常,8位已经足够,也有一些特殊应用需要16位(例如医学成像)。OpenCV还允许用其他类型的像素类型,创建矩阵(或图像),例如,整数(CV_32U 或 CV_32S)和浮点(CV_32F)数字。

这对于存储非常有用,例如,在某些图像处理任务中存储中间值。

大多数运算可以应用于任何类型的矩阵;另一些则需要特定类型的工作,仅使用给定数量的通道。因此,为了避免常见的编程错误,很好地理解函数或方法的前提条件是必不可少的。

1.1 操作像素值

只需要指明像素的行列位置,既可以获取到对应像素的值,对于灰度图就是一个数值,对于多通道的彩色图,将返回一个vector.

为了只管展示对像素的操作,简单实现一个椒盐噪声方法。

顾名思义,椒盐噪声是一种特殊类型的噪声,一些随机选择的像素被白色或黑色像素所取代。这种类型的噪声可能发生在故障通信中,某些像素值在传输过程中丢失。例子中,只是简单随机选中一些像素值进行赋值。

void salt(cv::Mat& image, int amount) {int rows,cols;for (size_t i = 0; i < amount; i++){//std::rand() function returns a value between 0 and RAND_MAXrows = rand() % image.rows;cols = rand() % image.cols;if (image.channels() == 1) {//灰度图像image.at<uchar>(rows, cols) = 255;}else { //彩色图像image.at<cv::Vec3b>(rows, cols)[0] = 255;image.at<cv::Vec3b>(rows, cols)[1] = 255;image.at<cv::Vec3b>(rows, cols)[2] = 255;}}
}

cv::Mat 包含多个对图片属性进行读写的方法。

对于公有的成员变量,如 rows 、cols 可以直接获取图片的高度和宽度。

对于像素的读写,可使用 at(int y, int x) 方法。但是,方法返回的类型必须在编译时已知,因为由于 cv:: mat 可以保存任何类型的元素,因此程序员需要指定预期的返回类型。

这也是为什么 at 方法被实现为 模板方法。所以,需要在调用时,指明类型:

image.at<uchar>(row, col) = 255;

注意:at 方法不会进行任何类型转换。所以,确保与像素类型匹配是编程人员的职责。

彩色图像中,每个像素有RGB三个分量。因此,cv::Mat 会返回包含三个 8-bit 值的 vector。 opencv 为这个类型定义了一个专用类型 cv::Vec3b . 对应的赋值语句就写成了如下方式:

//channel 代表0、1、2某个通道 
//opencv 以 BGR 的顺序存储数据,通道 0 代表 Blue 通道,以此类推。
image.at<cv::Vec3b>(row,col)[channel] = value;

对于两元素和四元素向量(cv::Vec2bandcv::Vec4b)以及其他元素类型也存在类似的向量类型。例如,对于双元素浮点向量,类型名的最后一个字母将被替换为 f , 即 cv::Vec2f 。在 short 类型的情况下,最后一个字母被替换为 s,对于 integer 被替换为 i,对于双精度浮点向量被替换为 d。

所有这些类型都是使用v::Vec<T,N>模板类定义的,其中 T 是类型,N 是 vector 元素的个数。

最后,你可能会发现图像传参,使用的是值传参方式。

这涉及前面的知识,对图像的普通复制时,它们仍然共享相同的图像数据。所以,当在函数内修改图片的内容时,不一定要通过引用来传输图片。顺便提一下,按值传递参数通常使编译器更容易进行代码优化。

1.2 cv::Mat_ 模板类

使用C++模板定义 cv::Mat ,使其变得通用。

使用 cv::Mat 的 at 方法,有时会很麻烦,因为必须在每次调用中将返回的类型,指定为模板参数。

在矩阵类型已知的情况下,以使用 cv::Mat_ ,它是cv::Mat的模板子类。这个类定义了一些额外的方法,但没有新的数据属性,因此指向一个类的指针或引用,可以直接转换到另一个类。在这些额外的方法中,有operator(),它允许直接访问矩阵元素。因此,如果一个图像是对应于 uchar 类型的 cv::Mat 变量,那么可以写下面的代码:

//使用 cv::Mat_ 模板
cv::Mat_<uchar> im(image);
// row 100, col 200 的像素被赋值 0
im(100,200) = 0;

由于在创建变量时声明了 cv::Mat_ 元素的类型,因此 operator() 方法在编译时就知道要返回哪种类型。这使得代码更简洁,与at 方法效果完全相同。

完整代码:

#include <iostream>
#include <opencv2/opencv.hpp>// 盐噪声
// 值传图像,因为是引用计数的方式,相当于浅拷贝,其实就会改变原图像
void salt(cv::Mat/*&*/ image, int amount)
{int row, col;for (size_t i = 0; i < amount; i++){// std::rand() function returns a value between 0 and RAND_MAXrow = rand() % image.rows;col = rand() % image.cols;if (image.channels() == 1){ // 灰度图像image.at<uchar>(row, col) = 255;}else{ // 彩色图像image.at<cv::Vec3b>(row, col)[0] = 255;image.at<cv::Vec3b>(row, col)[1] = 255;image.at<cv::Vec3b>(row, col)[2] = 255;}}
}// 椒噪声 尝试使用cv::Mat_<>
void pepper(cv::Mat image, int amount)
{int row, col;if (image.channels() == 1){ // 灰度图像cv::Mat_<uchar> temp(image);for (size_t i = 0; i < amount; i++){row = rand() % image.rows;col = rand() % image.cols;temp(row, col) = 0;}}else{ // 彩色图像cv::Mat_<cv::Vec3b> temp(image);for (size_t i = 0; i < amount; i++){row = rand() % image.rows;col = rand() % image.cols;temp(row, col) = cv::Vec3b(0, 0, 0);}}
}int main(int argc, char *argv[])
{// 检查命令行参数if (argc != 4){std::cerr << "Usage: " << argv[0] << " <input_image> <noise_rate:0.0 ~ 1.0> <output_image>" << std::endl;return -1;}// 读取输入图像和logo图像cv::Mat input_image = cv::imread(argv[1]);// 检查输入图像和logo图像是否成功读取if (input_image.empty()){std::cerr << "Error: Could not open or find input image" << std::endl;}cv::namedWindow("input_image", cv::WINDOW_NORMAL);cv::imshow("input_image", input_image);cv::waitKey(0);float noise_rate = std::stof(argv[2]);if (noise_rate < 0 || noise_rate > 1){std::cerr << "Error: noise_rate must be between 0.0 and 1.0" << std::endl;return -1;}// 细想,其实也不严谨,并不会严格贴近指定的噪声数量。可能存在重复赋值的问题// 只是简单控制下噪声点的数量而已salt(input_image, input_image.total() * noise_rate);pepper(input_image, input_image.total() * noise_rate);cv::imwrite(argv[3], input_image);cv::namedWindow("output_image", cv::WINDOW_NORMAL);cv::imshow("output_image", input_image);cv::waitKey(0);return 0;
}

效果:会增加许多的噪点请添加图片描述

http://www.dtcms.com/a/581074.html

相关文章:

  • Mamba YOLO: 基于状态空间模型的目标检测简单基线
  • Java 大视界 --Java 大数据在智慧农业农产品市场价格预测与种植决策支持中的应用实战
  • K8s的标签应用和调度
  • 如何应用动作捕捉技术让户外重体力工作更安全
  • rust中的Cargo.toml文件
  • PD快充诱骗芯片 XSP15 支持获取快充电压可与外部MCU共用D+D-网络与电脑传输数据
  • 蓝牙钥匙 第58次 蓝牙钥匙交互反馈设计:构建多感官无缝用户体验
  • spiderdemo第22题与webassembly的跨域
  • 【MySQL | 基础】通用语法及SQL分类
  • 【爬虫】分析天气网后,整理的一点理论上的理解
  • Web安全-文件上传漏洞-黑白名单及其它绕过思路(附思维导图)
  • WPF 高级 UI 定制:深入解析 VisualStateManager 与 Adorner
  • 全景相机市占率“罗生门”:影石的数据迷雾
  • 【2025】16届蓝桥杯 Java 组全题详解(省赛真题 + 思路 + 代码)
  • flas网站开发工具网店美工课程
  • 网站广告连接如何做wordpress.shop
  • Geobuilding模型转换,深圳市科技风贴图建筑物3dtiles倾斜摄影数据
  • CentOS 系统升级 OpenSSH 和 OpenSSL 的完整方案
  • PPIO上线Kimi K2 Thinking,兼容Anthropic协议
  • 本地项目上传至GitHub仓库标准操作手册
  • 如何做发表文章的网站网页设计模板图片家乡
  • 不停服务快速创建一个MySQL从节点加入已经存在的MGR集群中
  • TCP建立连接:三次握手(每次握手发的字段及字段值的解释)
  • 【SpringBoot】34 核心功能 - 指标监控- Spring Boot Actuator 指标监控开启与禁用与 Endpoint 定制
  • 【软考】信息系统项目管理师-资源管理论文范文
  • 标准nodejs项目工程
  • 定制网站开发公司种子网站模板
  • Maven前奏
  • C++面试高级篇——内存管理(一)
  • kanass零基础学习,如何进行工时管理,有效度量项目资源