前言
opencv的所有数据都是以一个mat存储的,可是我们需要对各个像素处理,这里必须高效的对像素快速的循环遍历,而矩阵对于像素的处理也具有得天独厚的优势。在这一篇博客中我们慢慢的学习一下。
正文
对于mat的循环便利也比较简单我们首先最容易想到的方法是:
Mat& ScanImageAndReduceC(Mat& I)
{
// accept only char type matrices
CV_Assert(I.depth() == CV_8U);
int channels = I.channels();
int nRows = I.rows;
int nCols = I.cols * channels;
if (I.isContinuous())
{
nCols *= nRows;
nRows = 1;
}
int i,j;
uchar* p;
for( i = 0; i < nRows; ++i)
{
p = I.ptr<uchar>(i);
for ( j = 0; j < nCols; ++j)
{
p[j] = table[p[j]];
}
}
return I;
}
但是如何使用呢我们看下主程序
#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
Mat& ScanImageAndReduceC(Mat& I)
{
// accept only char type matrices
CV_Assert(I.depth() == CV_8U);
int channels = I.channels();
int nRows = I.rows;
int nCols = I.cols * channels;
if (I.isContinuous())
{
nCols *= nRows;
nRows = 1;
}
int i,j;
uchar* p;
for( i = 0; i < nRows; ++i)
{
p = I.ptr<uchar>(i);
for ( j = 0; j < nCols; ++j)
{
p[j] = p[j]/4;
}
}
return I;
}
int main()
{
// 读入一张图片(游戏原画)
Mat img=imread("timg.jpeg");
ScanImageAndReduceC(img);
// 创建一个名为 "游戏原画"窗口
namedWindow("游戏原画");
// 在窗口中显示游戏原画
imshow("游戏原画",img);
// 等待6000 ms后窗口自动关闭
waitKey(6000);
}
这种明显效率更高我们下面看下迭代器方法:
Mat& ScanImageAndReduceC(Mat& I)
{
// accept only char type matrices
CV_Assert(I.depth() == CV_8U);
const int channels = I.channels();
switch(channels)
{
case 1:
{
MatIterator_<uchar> it, end;
for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
*it = table[*it];
break;
}
case 3:
{
MatIterator_<Vec3b> it, end;
for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
{
(*it)[0] = (*it)[0]/4;
(*it)[1] = (*it)[1]/4;
(*it)[2] = (*it)[2]/4;
}
}
}
return I;
}
这个效率明显比较第,下面我们看下一个更高效率的
void ScanImageAndReduceC(Mat& I)
{
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
p[i] = i/4;
LUT(img, lookUpTable, img);
return;
}
这个效率最高,是opencv自带的一个循环替换的函数,这里就不在详细介绍,大家可以自己查阅具体用法。
我们如果需要把像素中的过渡很尖锐的部分给平均掉,图片不会那么尖锐。图像处理很关键的过图像滤波都需要对像素通过卷积进行掩膜(mask)操作,其实相当于加了一层滤镜。下面看一个操作
I(i,j)=5∗I(i,j)−[I(i−1,j)+I(i+1,j)+I(i,j−1)+I(i,j+1)]⟺⎧⎩⎨⎪⎪I(i,j−1)I(i−1,j)I(i,j)I(i+1,j)I(i,j+1)⎫⎭⎬⎪⎪×⎧⎩⎨⎪⎪0−10−15−10−10⎫⎭⎬⎪⎪
这就是最简单的一个降噪的一个方法。这就是将中间的像素减去周围四个点像素至。操作很简单,关键下面是一个卷积计算,公式这里就不详细介绍,这就是矩阵的卷积运算,
我们看下这个加上滤镜的效果
忽然会发现没爱了,为啥我的处理吧图片变得模糊了呢,这里就是那个算子有问题啦,这里我暂时没详细看这部分的算法。详细程序如下:
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void Sharpen(const Mat& myImage,Mat& Result);
int main( int argc, char* argv[])
{
Mat src, dst0, dst1;
src = imread("timg.jpeg");
namedWindow("Input", WINDOW_AUTOSIZE);
namedWindow("Output", WINDOW_AUTOSIZE);
namedWindow("Output1", WINDOW_AUTOSIZE);
imshow( "Input", src );
double t = (double)getTickCount();
Sharpen( src, dst0 );
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Hand written function time passed in seconds: " << t << endl;
imshow( "Output", dst0 );
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
// t = (double)getTickCount();
filter2D( src, dst1, src.depth(), kernel );
// t = ((double)getTickCount() - t)/getTickFrequency();
// cout << "Built-in filter2D time passed in seconds: " << t << endl;
imshow( "Output1", dst1 );
waitKey();
getchar();
return 0;
}
void Sharpen(const Mat& myImage,Mat& Result)
{
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
const int nChannels = myImage.channels();
Result.create(myImage.size(),myImage.type());
for(int j = 1 ; j < myImage.rows-1; ++j)
{
const uchar* previous = myImage.ptr<uchar>(j - 1);
const uchar* current = myImage.ptr<uchar>(j );
const uchar* next = myImage.ptr<uchar>(j + 1);
uchar* output = Result.ptr<uchar>(j);
for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)
{
*output++ = saturate_cast<uchar>(5*current[i]
-current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);
}
}
Result.row(0).setTo(Scalar(0));
Result.row(Result.rows-1).setTo(Scalar(0));
Result.col(0).setTo(Scalar(0));
Result.col(Result.cols-1).setTo(Scalar(0));
}
这里包含了两个同一个操作的两种方法,明显opencv自带的更友好,方便使用。这里我们只关注opencv自导的函数:
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
filter2D( src, dst1, src.depth(), kernel );
这简直太简单有木有,我们记住这个操作,并且速度明显是经过优化的,更快。这里我们尽情享受opencv带给我们的便捷吧!
后记
终于写完了这写基础的知识点,多少内心还是有点纠结,有些地方觉得仅仅调用别人写好的东西,不过觉得了解就是生产力嘛,继续加油。