opencv-5-图像遍历与图像改变

opencvc++qt

目录

开始

图像的像素点访问与遍历

我们在上一篇文章中已经 大概介绍了 mat 图像的数据格式, 实际上可以理解为一个二维数组的格式, 那么 茴香豆的茴字一共有几种写法 访问一个像素点有几种方式呢

在opencv 的官方文档: How to scan images, lookup tables and time measurement with OpenCV

说了一共有三种, 常用的做法, 也可以参考Opencv访问图像像素的三种方法 这篇文章,

opencv 座标定义

opencv 对于图像数据的 座标是从左上角开始 的,

  • 纵向的座标 y 也称为行 rows
  • 横座标 y 也称为 列 cols

    座标范围: (0,0)-- (rows-1,cols-1) 我们一般使用 (行,列) 的方式进行访问,

也比如 我们使用 设置 行列尺寸的

但是对于 二维点, 实际上是 以 列行做 的尺寸, 此处也要进行注意

下标访问

对于二维数组, 肯定是使用 下标索引访问了

比如我们在上一篇文章中, 使用 lena_rgb.at<cv::Vec3b>(i, j) 进行彩色图像的访问, 使用lena_gray_avg.at<uchar>(i, j) 进行灰度图像的访问.

也就是 Matat() 方法进行图像的访问, 具体还要考虑灰度图像或者 彩色图像, 因为对于灰度图像只有一个值, 彩色图像每一个位置是有3个值的, 我们可以使用 lena_rgb.at<cv::Vec3b>(i, j)[k] 来 访问对应的 BGR 的值,


BGR 图像访问
// 遍历每一个像素进行灰度化
for (int i = 0; i < lena_rgb.rows; i++)
{
for (int j = 0; j < lena_rgb.cols; j++)
{
img_brg.at<cv::Vec3b>(i,j)[0] = 0; // 蓝色通道设为0
img_gray.at<uchar>(i,j) = 0; // 灰度设为 0
}
}

指针访问

图像数据是每行存储存储的, 我们可以每次获取到一行的数据 然后把行数据作为一维数组访问, 使用指针的方式就变得很简单了, 也是目前是最快的访问方式,

对于 灰度图像, 我们可以使用 uchar* pdata = img_gray.ptr<uchar>(i) 访问灰度图像一行的数据, 使用 cv::Vec3b* pdata = img_gray.ptr<cv::Vec3b>(i) 访问一行数据,

cv::Mat img_gray = cv::Mat::zeros(lena_rgb.size(), CV_8UC1);
cv::Mat img_bgr = cv::Mat::zeros(lena_rgb.size(), CV_8UC3);
// 使用指针进行图像访问
for (int i = 0; i < lena_rgb.rows; i++)
{
uchar *p_gray = img_gray.ptr<uchar>(i);
cv::Vec3b *p_bgr = img_bgr.ptr<cv::Vec3b>(i);
for (int j = 0; j < lena_rgb.cols; j++)
{
p_gray[j] = 0;
p_bgr[j][0] = 0;
}
}

我们通过行的索引, 获取到 第 i 行的数据指针, 然后使用作为一维数组的访问方式进行指针数据的访问,

功能强度, 十分快速, 但是可能会由于指针出现访问出错,

迭代器法访问

迭代器是 C++ 11(不确定) 之后的方案, 通过迭代器能够访问不连续的数据, 这样, 我们只需要给出图像的 开始地址与 结束地址就能完成图像的访问, 也是目前最安全的方案, 不会出现越界的错误

对于灰度图像或者 彩色图像, 我们都能够使用迭代器进行访问,

// 使用迭代器访问
for (cv::Mat_<cv::Vec3b>::iterator it = img_bgr.begin<cv::Vec3b>();
it != img_bgr.end<cv::Vec3b>(); it++)
{
(*it)[0] = 0;
}

遍历访问时间对比

在 opencv 的文档中, 给出了一个时间的对比方式, 通过获取 CPU 的运行时间 对比算法,

上面中, 我们给出了访问图像数据的三种方式, 这样我们就能进行一个一个像素的访问数据了,

其实, 我们在每个遍历的前后添加时间 测量程序, 最后得到这样的程序

#include "mainwindow.h"
#include <QApplication>
// 引入 opencv 函数头文件
#include <opencv2/opencv.hpp>
int main(int argc, char *argv[])
{
//QApplication a(argc, argv);
//MainWindow w;
//w.show();
// 设置 要显示的图像路径
//std::string test_pic = "./TestImages/lena.png";
double time_cnt = 0;
double time_s = 0.0; // 读取图像
// cv::Mat lena_rgb = cv::imread(test_pic);
// 声明 彩色图像 和灰度图像 // 设置 10000*10000 尺寸的图像, 避免出错
cv::Mat img_bgr = cv::Mat::zeros(cv::Size(1000, 1000), CV_8UC3); time_cnt = cv::getTickCount();
// 遍历每一个像素进行灰度化
for (int i = 0; i < img_bgr.rows; i++)
{
for (int j = 0; j < img_bgr.cols; j++)
{
img_bgr.at<cv::Vec3b>(i, j)[0] = 0;
}
}
time_s = ((double)cv::getTickCount() - time_cnt) / cv::getTickFrequency();
printf("index scan image time: \t\t %f second \n", time_s); time_cnt = cv::getTickCount();
// 使用指针进行图像访问
for (int i = 0; i < img_bgr.rows; i++)
{
cv::Vec3b *p_bgr = img_bgr.ptr<cv::Vec3b>(i);
for (int j = 0; j < img_bgr.cols; j++)
{
p_bgr[j][0] = 0; // 访问(i,j) 的第一个通道
}
}
time_s = ((double)cv::getTickCount() - time_cnt) / cv::getTickFrequency();
printf("pointer scan image time: \t %f second \n", time_s); time_cnt = cv::getTickCount();
// 使用迭代器访问
for (cv::Mat_<cv::Vec3b>::iterator it = img_bgr.begin<cv::Vec3b>();
it != img_bgr.end<cv::Vec3b>(); it++)
{
(*it)[0] = 0;
}
time_s = ((double)cv::getTickCount() - time_cnt) / cv::getTickFrequency();
printf("iterator scan image time: \t %f second \n", time_s); cv::waitKey(0); return 0;
// return a.exec();
}

最后,运行之后便能够得到我们的运行时间, 指针访问还是最快的方式,

index scan image time:           0.040871 second
pointer scan image time: 0.015297 second
iterator scan image time: 0.561931 second

差别还是有点大的, 使用 指针的方式是最快的, 迭代器是最安全的 , 但是 迭代器在较大尺寸的图的时候 是真的慢, 我们测试 的是 1000*1000 尺寸的图像, 时间差别还是比较大的, 在图像处理的过程中欧能够, 遍历图像还是比较常用的手段的, 所以 可以考虑考虑自己最熟悉的方式进行 图像遍历.. 性能情况下要多使用 指针方式访问, 注意具体的访问越界即可.

图像操作

我们在能够实现图像的像素点访问之后, 会想到干什么呢, opencv的 例程中给出了两个有用的案例

一个是将两幅图像做 混合叠加, 另外一个是处理图像的亮度和对比度

图像叠加

我们找两个等大的图像, 对于每一个点,像素相加除以2 得到平均值, 然后 生成新的图像

代码编写

感觉就是访问两幅图像, 然后叠加就好了, 跟上面讲的一样, 没有太多难度

// 设置 要显示的图像路径
std::string img_panda = "./TestImages/panda.png";
std::string img_lena = "./TestImages/lena.png"; // 读取两幅彩色图像 512*512
cv::Mat panda_bgr = cv::imread(img_panda);
cv::Mat lena_bgr = cv::imread(img_lena);
// 声明结果图像
cv::Mat res_bgr = cv::Mat::zeros(lena_bgr.size(), CV_8UC3); for (int i = 0; i < lena_bgr.rows; i++)
{
for (int j = 0; j < lena_bgr.cols; j++)
{
res_bgr.at<cv::Vec3b>(i, j)[0] = (panda_bgr.at<cv::Vec3b>(i, j)[0] + lena_bgr.at<cv::Vec3b>(i, j)[0]) / 2;
res_bgr.at<cv::Vec3b>(i, j)[1] = (panda_bgr.at<cv::Vec3b>(i, j)[1] + lena_bgr.at<cv::Vec3b>(i, j)[1]) / 2;
res_bgr.at<cv::Vec3b>(i, j)[2] = (panda_bgr.at<cv::Vec3b>(i, j)[2] + lena_bgr.at<cv::Vec3b>(i, j)[2]) / 2;
}
}
cv::imshow("panda_bgr", panda_bgr);
cv::imshow("lena_bgr", lena_bgr);
cv::imshow("res_bgr", res_bgr);
cv::waitKey(0);

执行结果

这种就是单纯像素的叠加, 没有什么深入的点, 理解就好了


执行结果

图像"拼接"

考虑一种拼接, 我们只是 将两幅图像并起来, 不考虑复杂的图像匹配, 我们可以简单的写一下, 也很简单

其实代码也很简单

// 设置 要显示的图像路径
std::string img_panda = "./TestImages/panda.png";
std::string img_lena = "./TestImages/lena.png"; // 读取两幅彩色图像 512*512
cv::Mat panda_bgr = cv::imread(img_panda);
cv::Mat lena_bgr = cv::imread(img_lena);
// 声明结果图像 1020*1020
cv::Mat res_bgr = cv::Mat::zeros(cv::Size(1024,1024), CV_8UC3); for (int i = 0; i < lena_bgr.rows; i++)
{
for (int j = 0; j < lena_bgr.cols; j++)
{
// 复制第一副图像
res_bgr.at<cv::Vec3b>(i, j)[0] = (panda_bgr.at<cv::Vec3b>(i, j)[0]);
res_bgr.at<cv::Vec3b>(i, j)[1] = (panda_bgr.at<cv::Vec3b>(i, j)[1]);
res_bgr.at<cv::Vec3b>(i, j)[2] = (panda_bgr.at<cv::Vec3b>(i, j)[2]); // 在第一副图下面 拼接 反色图像
res_bgr.at<cv::Vec3b>(512+i, j)[0] = (255- panda_bgr.at<cv::Vec3b>(i, j)[0]);
res_bgr.at<cv::Vec3b>(512+i, j)[1] = (255 - panda_bgr.at<cv::Vec3b>(i, j)[1]);
res_bgr.at<cv::Vec3b>(512+i, j)[2] = (255 -panda_bgr.at<cv::Vec3b>(i, j)[2]); // 复制第二幅图像
res_bgr.at<cv::Vec3b>(i, 512+j)[0] = (lena_bgr.at<cv::Vec3b>(i, j)[0]);
res_bgr.at<cv::Vec3b>(i, 512+j)[1] = (lena_bgr.at<cv::Vec3b>(i, j)[1]);
res_bgr.at<cv::Vec3b>(i, 512+j)[2] = (lena_bgr.at<cv::Vec3b>(i, j)[2]); // 在第二副图下面 拼接 反色图像
res_bgr.at<cv::Vec3b>(512 + i, 512+j)[0] = (255 - lena_bgr.at<cv::Vec3b>(i, j)[0]);
res_bgr.at<cv::Vec3b>(512 + i, 512+j)[1] = (255 - lena_bgr.at<cv::Vec3b>(i, j)[1]);
res_bgr.at<cv::Vec3b>(512 + i, 512+j)[2] = (255 - lena_bgr.at<cv::Vec3b>(i, j)[2]); }
}

"拼接" 图像

图像 相减

在考虑一种情况, 我们彩色图像的三个通道值有大有小, 那所有值减去最小值会得到什么呢,

看 代码:

for (int i = 0; i < lena_bgr.rows; i++)
{
for (int j = 0; j < lena_bgr.cols; j++)
{
// 求出最小值
cv::Vec3b tmp_px = lena_bgr.at<cv::Vec3b>(i, j);
int min_c = std::min(std::min(tmp_px[0], tmp_px[1]), tmp_px[2]); // 每个通道减去最小值
res_bgr.at<cv::Vec3b>(i, j)[0] = tmp_px[0] - min_c;
res_bgr.at<cv::Vec3b>(i, j)[1] = tmp_px[1] - min_c;
res_bgr.at<cv::Vec3b>(i, j)[2] = tmp_px[2] - min_c;
}
}

运行结果


图像相减

亮度和对比度操作

上面的两个操作只是玩玩, opencv 的例程中 关于

亮度和对比度的操作还是可以试试的Changing the contrast and brightness of an image!

亮度是指 数字图像的明暗程度

对比度是值 图像最高亮度与最低亮度的差值

锐度: 图像边缘像素的对比度

可以参考文章【数字图像处理系列二】亮度、对比度、饱和度、锐化、分辨率

其实吧, 知道就行了, 具体深究也可以看这篇一次搞懂清晰度、对比度以及锐化的区别, 有很多图片可以查看, 还能通过图像进行对比.

亮度操作

回到正题, 我们要进行 亮度变换 其实就是在进行 图像灰度值的调节过程.

是原始图像 灰度放大倍数 , 是灰度的偏置 bias

我们来实现一下, 看下效果:

参数选择 例程中的 ,

float a = 2.2f, b = 50;
for (int i = 0; i < lena_bgr.rows; i++)
{
for (int j = 0; j < lena_bgr.cols; j++)
{
// 取出原始图像 灰度值
cv::Vec3b tmp_px = lena_bgr.at<cv::Vec3b>(i, j);
// 每个通道减去最小值
res_bgr.at<cv::Vec3b>(i, j)[0] = cv::saturate_cast<uchar>(a * tmp_px[0] + b);
res_bgr.at<cv::Vec3b>(i, j)[1] = cv::saturate_cast<uchar>(a * tmp_px[1] + b);
res_bgr.at<cv::Vec3b>(i, j)[2] = cv::saturate_cast<uchar>(a * tmp_px[2] + b);
}
}

运行结果图:


亮度提升

这里使用的是 cv::saturate_cast<uchar> 进行的结果转换, 都是转换成 uchar 数据, 如果

直接使用 uchar 转换 得到的结果图像会很奇怪, 例如: res_bgr.at<cv::Vec3b>(i, j)[0] = (uchar)(a * tmp_px[0] + b);


直接使用 uchar 转换结果

伽马矫正(Gamma)

线性变化还有很多, 灰度转换, 截取, 反色等等操作, 但是有一种非线性变化, 必须要进行介绍, 那就是 伽马矫正, 用来对于矫正输入图像的亮度值,

具体的公式表示为:

对于不同的 值, 我们绘制输入输出曲线可以得到这个图,


gamma 矫正

我们测试一下代码试试, 例程中使用了 一个 LUT的函数,

因为看上面的变换公式, 涉及到了指数运算, 如果我们每个像素值都计算一次 会比较花时间, 反正对于一个像素值, 计算出来的gamma 值是一样的 , 我提前计算好, 之际查找不就好了吗,

我们在最开始计算出来每个 灰度值的结果表,

// 自定义 gamma 参数
float gamma = 0.4;
// 生成gamma 查找表
uchar table[256] = { 0 };
for (int i = 0; i < 256; i++)
{
table[i] = std::pow(i / 255.0f, gamma) * 255;
} for (int i = 0; i < lena_bgr.rows; i++)
{
for (int j = 0; j < lena_bgr.cols; j++)
{
// 取出原始图像 灰度值
cv::Vec3b tmp_px = lena_bgr.at<cv::Vec3b>(i, j);
// 每个通道减去最小值
res_bgr.at<cv::Vec3b>(i, j)[0] = table[tmp_px[0]];
res_bgr.at<cv::Vec3b>(i, j)[1] = table[tmp_px[1]];
res_bgr.at<cv::Vec3b>(i, j)[2] = table[tmp_px[2]];
}
}

lena 进行 gamma = 0.4 的运算结果

这个效果不是很好, 可以参考 opencv 例程里面的图, 效果真的很不错


opencv 例程 gamma

其他

opencv-5-图像遍历与图像改变的更多相关文章

  1. opencv——图像遍历以及像素操作

    摘要 我们在图像处理时经常会用到遍历图像像素点的方式,在OpenCV中一般有四种图像遍历的方式,在这里我们通过像素变换的点操作来实现对图像亮度和对比度的调整. 补充: 图像变换可以看成 像素变换--点 ...

  2. OpenCV学习笔记:resize函数改变图像的大小

    OpenCV提供了resize函数来改变图像的大小,函数原型如下: , , int interpolation=INTER_LINEAR ); 参数解释: src:输入,原图像,即待改变大小的图像: ...

  3. 第八节,Opencv的基本使用------存取图像、视频功能、简单信息标注工具

    1.存取图像 import cv2 img=cv2.imread('test.jpg') cv2.imwrite('test1.jpg',img) 2.图像的仿射变换 图像的仿射变换涉及图像的形状位置 ...

  4. OpenCV计算机视觉学习(11)——图像空间几何变换(图像缩放,图像旋转,图像翻转,图像平移,仿射变换,镜像变换)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 图像 ...

  5. 【OpenCV入门教程之三】 图像的载入,显示和输出 一站式完全解析(转)

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/20537737 作者:毛星云(浅墨)  ...

  6. 跟我一起学opencv 第四课之图像的基本操作

    1.图像是由像素组成的,所以修改了像素就可以实现图像的改变. 2先看灰度图像(单通道): *****2.获取灰度图像的像素值使用:  int gray = gray_src.at<uchar&g ...

  7. 第十三节,OPenCV学习(二)图像的简单几何变换

    图像的简单几何变换 几何变换不改变图像的像素值,只是在图像平面上进行像素的重新安排 适当的几何变换可以最大程度地消除由于成像角度.透视关系乃至镜头自身原因所造成的几何失真所产生的的负面影响. 一.图像 ...

  8. Python 图像处理 OpenCV (4):图像算数运算以及修改颜色空间

    前文传送门: 「Python 图像处理 OpenCV (1):入门」 「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」 「Python ...

  9. OpenCV计算机视觉学习(1)——图像基本操作(图像视频读取,ROI区域截取,常用cv函数解释)

    1,计算机眼中的图像 我们打开经典的 Lena图片,看看计算机是如何看待图片的: 我们点击图中的一个小格子,发现计算机会将其分为R,G,B三种通道.每个通道分别由一堆0~256之间的数字组成,那Ope ...

随机推荐

  1. Vue的父子组件v-model双向绑定,父组件修改子组件中绑定的v-model属性

    先来看下实现的效果,父组件中有个文本框,在点击下面按钮时弹出抽屉,抽屉里也有个文本框,文本框里的初始值要和父组件的文本框同步,并且修改抽屉里的文本框值时 父组件里的文本框值也要跟着改变 网上有大概三种 ...

  2. Django-CBV&FBV

    django中请求处理方式有2种:FBV 和 CBV 一.FBV FBV(function base views) 就是在视图里使用函数处理请求. urls.py from django.conf.u ...

  3. STL(六)——map、multimap

    STL--map.multimap 文章目录 STL--map.multimap 关联容器与map的介绍 map与set的异同 map与multimap的异同 map类对象的构造 map添加元素 ma ...

  4. scarpy爬虫框架

    目录 架构介绍 安装创建和启动 配置文件目录介绍 爬取数据,并解析 数据持久化 保存到文件 保存到redis 动作链,控制滑动的验证码 架构介绍 Scrapy一个开源和协作的框架,其最初是为了页面抓取 ...

  5. 1004 Counting Leaves (30 分)

    A family hierarchy is usually presented by a pedigree tree. Your job is to count those family member ...

  6. RMI 使用笔记

    Java 远程方法调用,即 Java RMI( Java Remote Method Invocation ) .顾名思义,可以使客户机上运行的程序能够调用远程服务器上的对象(方法). 下面主要介绍一 ...

  7. 本地代码上传到git仓库(github)

    准备:拥有自己的github账号:电脑上安装了git; 1.进入github,进入仓库点击NEW(新建仓库) 2.新建仓库 Repository name :仓库名称: Description (op ...

  8. 20 java 基础回顾--中阶引入

    一.数据类型 基本数据类型(共:四类八种) 整数 byte short int long 浮点 float double 字符 char 布尔 boolean 引用数据类型(new的数据) Stude ...

  9. Docker命名空间

    命名空间 命名空间( namespace )是 Linux 内核的一个强大特性,为容器虚拟化的实现带来极大便利,利用这 特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立的操作系 ...

  10. python 集合(set)和字典(dictionary)的用法解析

    Table of Contents generated with DocToc ditctaionary and set hash 介绍 集合-set 创建 操作和访问集合的元素 子集.超集.相对判断 ...