在准备开始的时候, 我大概列了一个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 中提供了 一种通用的方式进行处理 也就是核, 我们先看下实现方式
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);
我们设定核之后. 可以直接进行操作, 我们可以通过改动核从而进行图像处理, 看下图, 好像得到右侧的图像效果更好
两种实现结果是大概一致的, 算法上执行是一样的 , 那时间呢,
在之前的章节, 我们介绍了不同的图像遍历的方式进行图像遍历, 时间上差异还是比较大的, 这次我们同样使用了两种方式进行: 索引访问和指针访问进行图像处理, 算法部分的实现是一致的, 我们写了
testFunc
和 testFunc2
两个函数, 相应的代代码可以看下面
#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\t" << t << std::endl;
// 测试 指针方式进行 锐化运算
t = (double)cv::getTickCount();
res_img = testFunc2(src_img);
t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
std::cout << "sharpen-pointer: \t" << 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" << 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
我们在 modules\imgproc\src\filter.dispatch.cpp:1403
的位置看到了 filter2D
函数的定义
void filter2D(InputArray _src, OutputArray _dst, int ddepth,
InputArray _kernel, Point anchor0,
double delta, int borderType);
相应的我们去看 调用图
函数的主要调用使用了加速层的modules\imgproc\src\filter.dispatch.cpp:1307
处的 hal::filter2D
函数
这里涉及的部分还比较多, 可能也是由于调用的更底层的以及做了更多的边缘处理的原因 导致实际上花费的时间也更加的长,
这里暂时不去深究, 如果有机会再做进一步分析
原文:https://www.cnblogs.com/hugochen1024/p/12786092.html