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 图像访问
  1. // 遍历每一个像素进行灰度化
  2. for (int i = 0; i < lena_rgb.rows; i++)
  3. {
  4. for (int j = 0; j < lena_rgb.cols; j++)
  5. {
  6. img_brg.at<cv::Vec3b>(i,j)[0] = 0; // 蓝色通道设为0
  7. img_gray.at<uchar>(i,j) = 0; // 灰度设为 0
  8. }
  9. }

指针访问

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

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

  1. cv::Mat img_gray = cv::Mat::zeros(lena_rgb.size(), CV_8UC1);
  2. cv::Mat img_bgr = cv::Mat::zeros(lena_rgb.size(), CV_8UC3);
  3. // 使用指针进行图像访问
  4. for (int i = 0; i < lena_rgb.rows; i++)
  5. {
  6. uchar *p_gray = img_gray.ptr<uchar>(i);
  7. cv::Vec3b *p_bgr = img_bgr.ptr<cv::Vec3b>(i);
  8. for (int j = 0; j < lena_rgb.cols; j++)
  9. {
  10. p_gray[j] = 0;
  11. p_bgr[j][0] = 0;
  12. }
  13. }

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

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

迭代器法访问

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

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

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

遍历访问时间对比

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

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

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

  1. #include "mainwindow.h"
  2. #include <QApplication>
  3. // 引入 opencv 函数头文件
  4. #include <opencv2/opencv.hpp>
  5. int main(int argc, char *argv[])
  6. {
  7. //QApplication a(argc, argv);
  8. //MainWindow w;
  9. //w.show();
  10. // 设置 要显示的图像路径
  11. //std::string test_pic = "./TestImages/lena.png";
  12. double time_cnt = 0;
  13. double time_s = 0.0;
  14. // 读取图像
  15. // cv::Mat lena_rgb = cv::imread(test_pic);
  16. // 声明 彩色图像 和灰度图像 // 设置 10000*10000 尺寸的图像, 避免出错
  17. cv::Mat img_bgr = cv::Mat::zeros(cv::Size(1000, 1000), CV_8UC3);
  18. time_cnt = cv::getTickCount();
  19. // 遍历每一个像素进行灰度化
  20. for (int i = 0; i < img_bgr.rows; i++)
  21. {
  22. for (int j = 0; j < img_bgr.cols; j++)
  23. {
  24. img_bgr.at<cv::Vec3b>(i, j)[0] = 0;
  25. }
  26. }
  27. time_s = ((double)cv::getTickCount() - time_cnt) / cv::getTickFrequency();
  28. printf("index scan image time: \t\t %f second \n", time_s);
  29. time_cnt = cv::getTickCount();
  30. // 使用指针进行图像访问
  31. for (int i = 0; i < img_bgr.rows; i++)
  32. {
  33. cv::Vec3b *p_bgr = img_bgr.ptr<cv::Vec3b>(i);
  34. for (int j = 0; j < img_bgr.cols; j++)
  35. {
  36. p_bgr[j][0] = 0; // 访问(i,j) 的第一个通道
  37. }
  38. }
  39. time_s = ((double)cv::getTickCount() - time_cnt) / cv::getTickFrequency();
  40. printf("pointer scan image time: \t %f second \n", time_s);
  41. time_cnt = cv::getTickCount();
  42. // 使用迭代器访问
  43. for (cv::Mat_<cv::Vec3b>::iterator it = img_bgr.begin<cv::Vec3b>();
  44. it != img_bgr.end<cv::Vec3b>(); it++)
  45. {
  46. (*it)[0] = 0;
  47. }
  48. time_s = ((double)cv::getTickCount() - time_cnt) / cv::getTickFrequency();
  49. printf("iterator scan image time: \t %f second \n", time_s);
  50. cv::waitKey(0);
  51. return 0;
  52. // return a.exec();
  53. }

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

  1. index scan image time: 0.040871 second
  2. pointer scan image time: 0.015297 second
  3. iterator scan image time: 0.561931 second

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

图像操作

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

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

图像叠加

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

代码编写

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

  1. // 设置 要显示的图像路径
  2. std::string img_panda = "./TestImages/panda.png";
  3. std::string img_lena = "./TestImages/lena.png";
  4. // 读取两幅彩色图像 512*512
  5. cv::Mat panda_bgr = cv::imread(img_panda);
  6. cv::Mat lena_bgr = cv::imread(img_lena);
  7. // 声明结果图像
  8. cv::Mat res_bgr = cv::Mat::zeros(lena_bgr.size(), CV_8UC3);
  9. for (int i = 0; i < lena_bgr.rows; i++)
  10. {
  11. for (int j = 0; j < lena_bgr.cols; j++)
  12. {
  13. 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;
  14. 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;
  15. 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;
  16. }
  17. }
  18. cv::imshow("panda_bgr", panda_bgr);
  19. cv::imshow("lena_bgr", lena_bgr);
  20. cv::imshow("res_bgr", res_bgr);
  21. cv::waitKey(0);

执行结果

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


执行结果

图像"拼接"

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

其实代码也很简单

  1. // 设置 要显示的图像路径
  2. std::string img_panda = "./TestImages/panda.png";
  3. std::string img_lena = "./TestImages/lena.png";
  4. // 读取两幅彩色图像 512*512
  5. cv::Mat panda_bgr = cv::imread(img_panda);
  6. cv::Mat lena_bgr = cv::imread(img_lena);
  7. // 声明结果图像 1020*1020
  8. cv::Mat res_bgr = cv::Mat::zeros(cv::Size(1024,1024), CV_8UC3);
  9. for (int i = 0; i < lena_bgr.rows; i++)
  10. {
  11. for (int j = 0; j < lena_bgr.cols; j++)
  12. {
  13. // 复制第一副图像
  14. res_bgr.at<cv::Vec3b>(i, j)[0] = (panda_bgr.at<cv::Vec3b>(i, j)[0]);
  15. res_bgr.at<cv::Vec3b>(i, j)[1] = (panda_bgr.at<cv::Vec3b>(i, j)[1]);
  16. res_bgr.at<cv::Vec3b>(i, j)[2] = (panda_bgr.at<cv::Vec3b>(i, j)[2]);
  17. // 在第一副图下面 拼接 反色图像
  18. res_bgr.at<cv::Vec3b>(512+i, j)[0] = (255- panda_bgr.at<cv::Vec3b>(i, j)[0]);
  19. res_bgr.at<cv::Vec3b>(512+i, j)[1] = (255 - panda_bgr.at<cv::Vec3b>(i, j)[1]);
  20. res_bgr.at<cv::Vec3b>(512+i, j)[2] = (255 -panda_bgr.at<cv::Vec3b>(i, j)[2]);
  21. // 复制第二幅图像
  22. res_bgr.at<cv::Vec3b>(i, 512+j)[0] = (lena_bgr.at<cv::Vec3b>(i, j)[0]);
  23. res_bgr.at<cv::Vec3b>(i, 512+j)[1] = (lena_bgr.at<cv::Vec3b>(i, j)[1]);
  24. res_bgr.at<cv::Vec3b>(i, 512+j)[2] = (lena_bgr.at<cv::Vec3b>(i, j)[2]);
  25. // 在第二副图下面 拼接 反色图像
  26. res_bgr.at<cv::Vec3b>(512 + i, 512+j)[0] = (255 - lena_bgr.at<cv::Vec3b>(i, j)[0]);
  27. res_bgr.at<cv::Vec3b>(512 + i, 512+j)[1] = (255 - lena_bgr.at<cv::Vec3b>(i, j)[1]);
  28. res_bgr.at<cv::Vec3b>(512 + i, 512+j)[2] = (255 - lena_bgr.at<cv::Vec3b>(i, j)[2]);
  29. }
  30. }

"拼接" 图像

图像 相减

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

看 代码:

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

运行结果


图像相减

亮度和对比度操作

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

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

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

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

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

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

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

亮度操作

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

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

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

参数选择 例程中的 ,

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

运行结果图:


亮度提升

这里使用的是 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 值是一样的 , 我提前计算好, 之际查找不就好了吗,

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

  1. // 自定义 gamma 参数
  2. float gamma = 0.4;
  3. // 生成gamma 查找表
  4. uchar table[256] = { 0 };
  5. for (int i = 0; i < 256; i++)
  6. {
  7. table[i] = std::pow(i / 255.0f, gamma) * 255;
  8. }
  9. for (int i = 0; i < lena_bgr.rows; i++)
  10. {
  11. for (int j = 0; j < lena_bgr.cols; j++)
  12. {
  13. // 取出原始图像 灰度值
  14. cv::Vec3b tmp_px = lena_bgr.at<cv::Vec3b>(i, j);
  15. // 每个通道减去最小值
  16. res_bgr.at<cv::Vec3b>(i, j)[0] = table[tmp_px[0]];
  17. res_bgr.at<cv::Vec3b>(i, j)[1] = table[tmp_px[1]];
  18. res_bgr.at<cv::Vec3b>(i, j)[2] = table[tmp_px[2]];
  19. }
  20. }

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. 【Mongodb】聚合查询 && 固定集合

    概述 数据存储是为了可查询,统计.若数据只需存储,不需要查询,这种数据也没有多大价值 本篇介绍Mongodb 聚合查询(Aggregation) 固定集合(Capped Collections) 准备 ...

  2. ESLint如何配置

    1.简介 通过用 ESLint 来检查一些规则,我们可以: 统一代码风格规则,如:代码缩进用几个空格:是否用驼峰命名法来命名变量和函数名等. 减少错误, 如:相等比较必须用 === ,变量在使用前必须 ...

  3. MySQL学习之路3-MySQL中常用数据类型

    MySQL中常用数据类型 字符型 存储字符型数据.例如姓名,地址,电话号码等.使用引号括起来,一般使用单引号. 常用类型: char(255) 定长字符串,最大长度255个字符. varchar(25 ...

  4. 【python实现卷积神经网络】激活函数的实现(sigmoid、softmax、tanh、relu、leakyrelu、elu、selu、softplus)

    代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...

  5. std::string 字符串分割

    #include <iostream> #include <string> #include <vector> std::vector<std::string ...

  6. Js 事件原理与事件委托

    事件原理三阶段 捕获(有外向内).目标.冒泡(由内向外) 事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点( ...

  7. 💕《给产品经理讲JVM》:垃圾收集器

    前言 在上篇中,我们把 JVM 中的垃圾收集算法有了一个大概的了解,又是一个阴雨连绵的周末,宅在家里的我们又开始了新一轮的学习: 产品大大:上周末我们说了垃圾收集算法,下面是不是要讲一下这些算法的应用 ...

  8. 关于树的重心--POJ 1655

    树的重心的定义: 在一棵树中,找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡. 通俗来说就是以这个点为根节点,找到他最大的衣蛾子树,然后 ...

  9. SpringBoot 集成 Elasticsearch

    前面在 ubuntu 完成安装 elasticsearch,现在我们SpringBoot将集成elasticsearch. 1.创建SpringBoot项目 我们这边直接引入NoSql中Spring ...

  10. springboot集成JdbcTemplate+druid

    application.yml datasource: username: root password: root url: jdbc:mysql://localhost:3306/early_war ...