zoukankan      html  css  js  c++  java
  • opencv-5-图像遍历与图像改变

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

    目录

    开始

    图像的像素点访问与遍历

    我们在上一篇文章中已经 大概介绍了 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 图像访问
    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: 		 %f second 
    ", 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: 	 %f second 
    ", 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: 	 %f second 
    ", 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 转换结果
    直接使用 uchar 转换结果

    伽马矫正(Gamma)

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

    具体的公式表示为:

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

    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 的运算结果
    lena 进行 gamma = 0.4 的运算结果

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

    opencv 例程 gamma
    opencv 例程 gamma

    其他

  • 相关阅读:
    HDU 5313 bitset优化背包
    bzoj 2595 斯坦纳树
    COJ 1287 求匹配串在模式串中出现的次数
    HDU 5381 The sum of gcd
    POJ 1739
    HDU 3377 插头dp
    HDU 1693 二进制表示的简单插头dp
    HDU 5353
    URAL 1519 基础插头DP
    UVA 10294 等价类计数
  • 原文地址:https://www.cnblogs.com/hugochen1024/p/12758526.html
Copyright © 2011-2022 走看看