zoukankan      html  css  js  c++  java
  • opencv-8-图像核与蒙板操作

    opencv-8-图像核与蒙板操作

    开始之前

    在准备开始的时候, 我大概列了一个opencv 章节列表, 按照章节进行写, 写到某些部分的时候再具体调整章节内容, 完成了之后, 会将具体的章节链接更新到这个列表中 算是作为一个目录吧.
    有的章节写到很快, 有的章节写的很慢, 但是我会坚持一直写下去

    目录

    开始

    按照我的写作计划, 之前算是完成了前面的大的章节, 我们开始正式进入图像处理的章节了, 在之前的章节中,我们介绍了图像的遍历操作, 我们从一个基础的问题出发, 我们对于每一点的像素值, 每个点减去他上面边的点的值作为结果值, 那我们会得到什么图呢,

    图像下侧差分

    这我们为了简单运算吧, 我们提前将结果初始化为0, 然后将每一行的像素减去它上面的像素, 作为结果当前点的颜色值 我们看下代码以及跑起来看下会是什么结果

    int main(int argc, char *argv[])
    {
        // 设置 要显示的图像路径
        std::string lena_png = "./TestImages/lena.png";
        cv::Mat src_img = cv::imread(lena_png);
        cv::Mat res_img = cv::Mat::zeros(src_img.size(), CV_8UC3);
        // 初始化所有结果为 0 第一行不存在上一行, 默认为0  彩色图像 每个通道都计算
        for (int i = 1; i < src_img.rows; i++)
        {
            for (int j = 0; j < src_img.cols; j++)
            {
                for (int k = 0; k < src_img.channels(); k++)
                {
                    res_img.at<cv::Vec3b>(i, j)[k] = src_img.at<cv::Vec3b>(i, j)[k] - src_img.at<cv::Vec3b>(i-1, j)[k];
                }
            }
        }
    
        cv::imshow("src_img", src_img);
        cv::imshow("res_img", res_img);
    
        cv::waitKey(0);
    
        return 0;
        // return a.exec();
    }

    其中核心部分就是计算 res_img.at<cv::Vec3b>(i, j)[k] = src_img.at<cv::Vec3b>(i, j)[k] - src_img.at<cv::Vec3b>(i-1, j)[k]; 颜色部分, 我们的 i 从第一行开始的 所以不会出现索引出错, 这个操作比较简单, 我们得到了下面的图像结果,

    下差分图
    下差分图

    图像锐化操作

    我们这里说一下, 在之前的章节也都提过, 在图像处理的过程中, 我们一般采用的是灰度图像, 能够有效的获取到图像的细节特征, 而且计算起来比较方便, 所以我们在后续进行一下算法处理的时候会采用灰度图像, 特此说明

    根据opencv 例程Mask operations on matrices 中提到的一个案例, 我计算一个点与它四邻域的的差值的 也就是


    在线性代数中, 我们的运算都能转换成矩阵的运算, 那么, 我们抽象一下, 我们能够得到这样的一个结果,

    与我们的运算得到的等式是一致的, 我们考虑一下怎么实现, 这里我们也参考 例程里面的实现,
    我们将算法部分封装起来 这里我们使用cv::Mat res_img = testFunc(src_img); 这样的方法, 然后主要去实现 testFunc函数就行了, 后面我们为了不再重复的贴出代码, 希望之后看到的话 不要有疑问.

    #include "mainwindow.h"
    #include <QApplication>
    // 引入 opencv 函数头文件
    #include <opencv2/opencv.hpp>
    
    // 进行 测试 算法
    cv::Mat testFunc(const cv::Mat &src_img)
    {
        cv::Mat res_img = cv::Mat::zeros(src_img.size(), CV_8UC1);
    
        for (int i = 1; i < src_img.rows - 1; i++)
        {
            for (int j = 1; j < src_img.cols - 1; j++)
            {
                res_img.at<uchar>(i, j) = cv::saturate_cast<uchar>( src_img.at<uchar>(i, j)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i - 1, j)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i + 1, j)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i, j - 1)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i, j + 1));
            }
        }
        return res_img;
    }
    int main(int argc, char *argv[])
    {
        // 设置 要显示的图像路径
        std::string lena_png = "./TestImages/lena.png";
        cv::Mat src_img = cv::imread(lena_png);
        cv::cvtColor(src_img, src_img, cv::COLOR_BGR2GRAY);
    
        cv::Mat res_img = testFunc(src_img);
    
        cv::imshow("src_img", src_img);
        cv::imshow("res_img", res_img);
        cv::waitKey(0);
        return 0;
        // return a.exec();
    }

    我们上面提出的算法就是在进行图像的锐化操作,相当于在原始像素的基础上加上了我们原图与四邻域像素的差值, 这样我们能够将边缘梯度过大的区域进行增强, 平滑部分则不会过分处理, 最终得到这样的图像处理结果..

    锐化增强效果
    锐化增强效果

    opencv 核操作

    我们在处理的时候实际上没有解决边缘的问题, 在结果图中可以看到四个边缘各有一个像素的黑色边缘, 我们可以考虑计算其他的简化计算方式, 但是太过与繁琐了, 为了优化体验我们就没有处理, 但是 opencv 中提供了 一种通用的方式进行处理 也就是核, 我们先看下实现方式

    cv::Mat kernel = (cv::Mat_<char>(3, 3) << 0, -1, 0,
    										-1, 5, -1,
    										0, -1, 0);
    cv::Mat res_img2;
    cv::filter2D(src_img, res_img2, src_img.depth(), kernel);

    我们设定核之后. 可以直接进行操作, 我们可以通过改动核从而进行图像处理, 看下图, 好像得到右侧的图像效果更好

    锐化效果对比
    锐化效果对比

    运行时间对比

    两种实现结果是大概一致的, 算法上执行是一样的 , 那时间呢,
    在之前的章节, 我们介绍了不同的图像遍历的方式进行图像遍历, 时间上差异还是比较大的, 这次我们同样使用了两种方式进行: 索引访问和指针访问进行图像处理, 算法部分的实现是一致的, 我们写了
    testFunctestFunc2 两个函数, 相应的代代码可以看下面

    #include "mainwindow.h"
    #include <QApplication>
    // 引入 opencv 函数头文件
    #include <opencv2/opencv.hpp>
    
    // 进行 测试 算法
    cv::Mat testFunc(const cv::Mat &src_img)
    {
        cv::Mat res_img = cv::Mat::zeros(src_img.size(), CV_8UC1);
    
        for (int i = 1; i < src_img.rows - 1; i++)
        {
            for (int j = 1; j < src_img.cols - 1; j++)
            {
                res_img.at<uchar>(i, j) = cv::saturate_cast<uchar>(src_img.at<uchar>(i, j)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i - 1, j)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i + 1, j)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i, j - 1)
                    + src_img.at<uchar>(i, j) - src_img.at<uchar>(i, j + 1));
            }
        }
        return res_img;
    }
    // 使用测试 指针函数
    cv::Mat testFunc2(const cv::Mat &src_img)
    {
        cv::Mat res_img = cv::Mat::zeros(src_img.size(), CV_8UC1);
    
        for (int i = 1; i < src_img.rows - 1; i++)
        {
            const uchar* p_row_pre = src_img.ptr<uchar>(i - 1);
            const uchar* p_row_cur = src_img.ptr<uchar>(i);
            const uchar* p_row_next = src_img.ptr<uchar>(i + 1);
    
            uchar* p_row_res = res_img.ptr<uchar>(i);
            for (int j = 1; j < src_img.cols - 1; j++)
            {
                *p_row_res++ = cv::saturate_cast<uchar>(5 * p_row_cur[j]
                    - p_row_cur[j-1] - p_row_cur[j+1] - p_row_pre[j] - p_row_next[j]);
            }
        }
        return res_img;
    }
    
    int main(int argc, char *argv[])
    {
        // 设置 要显示的图像路径
        std::string lena_png = "./TestImages/lena.png";
        cv::Mat src_img = cv::imread(lena_png);
        cv::cvtColor(src_img, src_img, cv::COLOR_BGR2GRAY);
    
        // 测试索引方式进行 锐化运算
        double t = (double)cv::getTickCount();
        cv::Mat res_img = testFunc(src_img);
        t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
        std::cout << "sharpen-index: 		" << t << std::endl;
    
        // 测试 指针方式进行 锐化运算
        t = (double)cv::getTickCount();
        res_img = testFunc2(src_img);
        t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
        std::cout << "sharpen-pointer: 	" << t << std::endl;
        
        cv::Mat kernel = (cv::Mat_<char>(3, 3) << 0, -1, 0,
            -1, 5, -1,
            0, -1, 0);
        cv::Mat res_img2;
    
        // 测试 filter 2D 算法时间
        t = (double)cv::getTickCount();
        cv::filter2D(src_img, res_img2, src_img.depth(), kernel);
        t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
        std::cout << "sharpen-filter: 	" << t << std::endl;
    
        cv::imshow("src_img", src_img);
        cv::imshow("res_img", res_img);
        cv::imshow("res_img2", res_img2);
        cv::waitKey(0);
        return 0;
        // return a.exec();
    }

    算法实现上很简单, 就是我们上面提到得到方法, 结果以是接近一致的, 但是时间上差的还是比较多, 使用 filter2D 的方式访问得到的图像还是比较好看的, 运行时间也是要比我们自己通过索引方式进行的算法要快很很多的, 但是相比我们使用指针还是有所不如,

    sharpen-index:          0.0747024 s
    sharpen-pointer:        0.0040774 s
    sharpen-filter:         0.0416613 s

    我们在 modulesimgprocsrcfilter.dispatch.cpp:1403 的位置看到了 filter2D 函数的定义

    void filter2D(InputArray _src, OutputArray _dst, int ddepth,
                  InputArray _kernel, Point anchor0,
                  double delta, int borderType);

    相应的我们去看 调用图

    filter2D 函数调用图
    filter2D 函数调用图

    函数的主要调用使用了加速层的modulesimgprocsrcfilter.dispatch.cpp:1307 处的 hal::filter2D 函数
    这里涉及的部分还比较多, 可能也是由于调用的更底层的以及做了更多的边缘处理的原因 导致实际上花费的时间也更加的长,

    这里暂时不去深究, 如果有机会再做进一步分析

  • 相关阅读:
    JS 集合
    JS 字典
    JS 链表
    JS 队列
    JS 栈
    JS 列表
    JS 数组
    IOS 提示无法下载程式问题
    ubuntu 下安装Go开发环境
    菜鸟看Redis(一)
  • 原文地址:https://www.cnblogs.com/hugochen1024/p/12786092.html
Copyright © 2011-2022 走看看