资源描述
一:Opencv2与opencv1的区别:
Opencv1.0版本于2006年面世,主要基于C语言。2009年发布opencv2,主要基于C++。此时opencv库被划分成多个模块,这些模块被编译成库文件后,位于lib文件夹中。主要有以下模块(版本1的结构见我的这篇blog:
Opencv_core模块:包含核心功能,尤其是底层数据结构和算法函数。
Opencv_improc模块:包含图像处理函数。
Opencv_highgui模块:包含读写图像及视频的函数,以及操作图形用户界面函数。
Opencv_features2d模块:包含兴趣点检测子,描述子以及兴趣点匹配框架。
Opencv_calib3d模块:包含相机标定,双目几何估计以及立体视觉函数。
Opencv_video模块:包含运动估算,特征跟踪以及前景提取函数与类。
Opencv_objdetect模块:包括物体检测函数,如脸部和行人检测。
库中还包含其它的工具模块,如机器学习(opencv_ml),计算几何(opencv_flann),第三方代码(opencv_contrib)等。这些模块都对有一个单独的头文件(位于include文件夹)。推荐的声明方式如下:
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
而#include "cv.h"这是旧的代码方式,那是库还没有被划分为模块。
二:读取、显示和保存图片
(1)代码:
[cpp] view plaincopyprint?
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("F:\\tongtong.jpg", 1); //读取图片
if(!image.data) // data指向已分配内存块的指针
{
cout << "fail to load image" << endl;
}
cout << "image size: " << image.size().height << "," << image.size().width << endl; //size()返回的是一个结构体
namedWindow("show");
imshow("show", image); // 显示图片
imwrite("F:\\tongtong2.jpg", image);
<span style="white-space:pre"> </span>Mat result;
flip(image,result, 0);
namedWindow("result",0);
imshow("result", result);
waitKey(0);
return 0;
}
(2)Explaination:
<1>opencv2中用于存储图像数据为Mat类型,而在opencv1中用IplImage(详细见我的这篇blog:
<2>image.data是指向已分配的内存块的指针,当图片没有加载进来,则为NULL。
<3>image.size()返回的是一个结构体,实际上包括width和height这两个成员变量。
<4>flip(image,result,0); //其中正数表示水平反转,0表示垂直反转,负数表示既有水平又有垂直反转。
3)结果:
三:深浅拷贝
(1) 浅拷贝:
Mat B;
B = image ; // 第一种方式
Mat C(image); // 第二种方式
这两种方式称为浅copy,是由于它们有不同的矩阵头,但是它们共享内存空间,即指向一个矩阵。当图像矩阵发生变化时,两者相关联,都会变化。
(2) 深拷贝:
Mat B,C;
B = image.clone(); // 第一种方式
image.copyTo(C); // 第二种方式
深拷贝是真正的copy了一个新的图像矩阵,此时image,B,C三者相互没有影响。
四:IplImage装换为Mat
IplImage*iplImage = cvLoadImage("F:\\11.jpg",1);
Mat image2(iplImage,false);
// 其中false为浅拷贝,而true为深拷贝,默认为false。此时注意需要释放iplImage
当然opencv提供了另外一种指针类,无需手动释放,但现在已经不用了,可以使用它来封装IplImage指针:
Ptr<IplImage> iplImage = cvLoadImage("F:\\11.jpg",1);
图像遍历主要有三种方法,本节主要介绍和比较这三种方法。
一:简单存取像素值
首先介绍一个名词—椒盐噪点:它是一种特殊的噪点,它随机的将图像中的部分像素设置为白色或者黑色。
Code:
[cpp] view plaincopyprint?
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void salt(Mat &image, int n)
{
for(int k = 0; k < n; k++)
{
rand();
int i = rand() % image.cols;
int j = rand() % image.rows;
if(image.channels() == 1)
{
image.at<uchar>(j,i) = 255;
}
else if(image.channels() == 3)
{
image.at<Vec3b>(j,i)[0] = 255;
image.at<Vec3b>(j,i)[1] = 255;
image.at<Vec3b>(j,i)[2] = 255;
}
}
}
void salt2(Mat_<Vec3b> image, int n) // 这个只针对彩色三通道的图片
{
for(int k = 0; k < n; k++)
{
rand();
int i = rand() % image.cols;
int j = rand() % image.rows;
if(image.channels() == 3)
{
image(j,i)[0] = 255;
image(j,i)[1] = 255;
image(j,i)[2] = 255;
}
}
}
int main()
{
Mat image = imread("F:\\huangrong.jpg", 1);
salt(image, 3000);
namedWindow("image");
imshow("image", image);
waitKey(0);
return 0;
}
Explaination:
(1) rand()是随机数生成函数。
(2)image.at<Vec3b>(j,i)[0] = 255;
成员函数at(inty, int x)可以用来存取图像元素,但必须在编译时期知道它的数据类型,这样的函数被称为模板函数,即需要指定操作成员的类型at<uchar>(y,x)
Opencv 中的类型Vec3b是由三个uchar组成的向量,可以通过操作符[]来获取。
(3)Mat_<Vec3b> image; image(j,i)[0] =255;
Mat_是Mat的一个模板子类。在事先知道矩阵类型的情况下,使用Mat_可以带来一些便利。这个类定义了一些额外的成员方法,但没有定义成员变量。它重载了操作符(),允许我们可以通过它直接存取矩阵元素。
Result:
二:通过指针遍历图像
需要说明的是:一个宽为W,高为H的三通道图像需要填补一个大小由W*H*3个uchar构成的内存块。但是,出于效率的考虑,图像会在每行行尾填补一些额外的像素。这是因为,如果行的长度是4或8的倍数,一些多媒体处理芯片可以更高效的处理图像。
Mat中的成员函数isContinuous()可以判断这幅图像是否对行进行了填补。如果为真,则没有填补。
Code:
[cpp] view plaincopyprint?
/*通过指针遍历图像*/
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void colorReduce(Mat &image, int div = 64) // 颜色缩减函数
{
/*if(image.isContinuous())
{
image.reshape(3, image.cols *image.rows);
cout << image.rows << endl;
cout << image.cols << endl;
}*/
int n1 = image.rows;
int nc = image.cols * image.channels();
if(image.isContinuous())
{
nc = n1 * nc;
n1 = 1;
}
for(int j = 0; j < n1; j++)
{
uchar *data = image.ptr(j);
//uchar *data = image.data + j * image.step; // 得到指向第i行的地址有两种方法 ptr是成员函数,而data是成员变量,不推荐使用data,易出错
for(int i = 0; i < nc; i++)
{
data[i] = data[i]/div *div + div/2;
}
}
cout << "一行含有的字节数:" << image.step << endl;
cout << "一个像素所含有的字节:" << image.elemSize() << endl;
cout << "通道数:" << image.channels()<< endl;
cout << "总像素个数:" << image.total() << endl;
cout << "width:" << image.size().width << endl;
cout << "width:" << image.cols << endl;
cout << "height:" << image.rows << endl;
}
void colorReduce2(const Mat &image, Mat &result, int div = 64)
{
int n1 = image.rows;
//int nc = image.cols * image.channels();
int nc = image.cols ;
for(int j = 0; j < n1; j++)
{
//uchar *data = image.ptr(j);
//uchar *data_in = image.data + j * image.step; // 得到指向第i行的地址有两种方法 ptr是成员函数,而data是成员变量
//uchar *data_out = result.data + j * result.step;
for(int i = 0; i < nc; i++)
{
uchar *data = image.data + j * image.step + i * image.elemSize(); // 这种方式不推荐使用,一方面容易出错,还不适用于带有"感兴趣区域"
//data_out[i] = data_in[i]/div *div + div/2;
data[0] = 0;
data[1] = 0;
data[2] = 0;
}
}
}
int main()
{
Mat image = imread("F:\\huangrong.jpg", 1);
Mat result;
result.create(Size(image.cols, image.rows),image.type()); // create函数为result创建一个与输入图像的尺寸和类型完全相同的矩阵
//colorReduce2(image, result);
colorReduce(image);
namedWindow("image");
imshow("image", image);
//namedWindow("result");
//imshow("result", result);
waitKey(0);
return 0;
}
Explaination:
(1) 得到指向第j行的地址有两种方法: image.ptr(j)是成员函数,而data是成员变量image.data+ j * image.step;但后面一种方法复杂点,推荐使用前者。
(2)成员变量step,rows,cols,成员函数elemSize,channels,total,size,create等解释见代码注释。
Result:
三:通过迭代器遍历图像
C++中迭代器不熟的可以看看我转载的一篇blog:
Code:
[cpp] view plaincopyprint?
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void colorReduce(Mat &image, int div = 64)
{
//Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
//Mat_<Vec3b>::iterator itend = image.end<Vec3b>();
MatIterator_<Vec3b> it = image.begin<Vec3b>(); // 第二种迭代器方式
MatIterator_<Vec3b> itend = image.end<Vec3b>();
for(; it != itend; ++it)
{
(*it)[0] = (*it)[0]/div * div + div/2;
(*it)[1] = (*it)[1]/div * div + div/2;
(*it)[2] = (*it)[2]/div * div + div/2;
}
}
void colorReduce2(Mat_<Vec3b> image, int div = 64)
{
//Mat_<Vec3b>::iterator it = image.begin(); // const_iterator
//Mat_<Vec3b>::iterator itend = image.end();
MatIterator_<Vec3b> it = image.begin(); // 第二种迭代器方式
MatIterator_<Vec3b> itend = image.end(); // 常量迭代器 MatConstIterator_ Mat 实例不能修改
for(; it != itend; ++it)
{
(*it)[0] = (*it)[0]/div * div + div/2;
(*it)[1] = (*it)[1]/div * div + div/2;
(*it)[2] = (*it)[2]/div * div + div/2;
}
}
int main()
{
Mat image = imread("F:\\huangrong.jpg", 1);
if(!image.data){
cout << "fail to load image" << endl;
return 0;
}
colorReduce(image);
namedWindow("image");
imshow("image", image);
waitKey(0);
return 0;
}
Explaination:
(1)定义迭代器有两种方法:
MatIterator_<Vec3b>it ; //它是一个Mat实例的迭代器,是一个模板类
Mat_<Vec3b>::iterator it; // 它是定义在Mat_内部的迭代器
<Vec3b>表明是迭代器的类型,各个颜色分量可以通过操作符[]得到。
(2)获得迭代器开始和结束位置也有两种方法:
image.begin<Vec3b>(); //image 是Mat类型,Mat的方法
image.begin(); // image是Mat_类型,通过Mat_实例得到的
(3)指向的mat是const类型所对应的迭代器,此时Mat对象不能修改。
MatConstIterator_<Vec3b>it ; //它是一个Mat实例的迭代器,是一个模板类
Mat_<Vec3b>::const_iteratorit; // 它是定义在Mat_内部的迭代器
Result:
四:三种方法的时间效率比较:
Opencv中提供了两个非常实用的函数getTickCount()和getTickFrequency()可以非常方便的计算一段代码运行的时间;
代码:
[cpp] view plaincopyprint?
void timeCalc(Mat &image){
double duration;
duration = static_cast<double>(getTickCount()); // CPU 滴答的次数
colorReduce(image);
duration = static_cast<double>(getTickCount()) - duration;
duration /= getTickFrequency(); // 运行时间,以ms为单位 //CPU 每ms滴答的次数
}
首先讲下:
static_case<type_id>(expression)
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
通过以上计算代码运行时间的方法我们可以得出上面三种遍历方法的运行时间:其中指针遍历时间最快,而迭代遍历最慢。可以说迭代器遍历代码简单,是一种最安全的遍历方法,出错率低。而简单的存取方法一般用来对随机位置的像素进行读写,并不适合用来遍历图像。遍历一般用指针。最后需要说明的是一次性处理一个像素的三个通道比循环处理每个通道所花费的时间要少,效率要高。
一:邻域操作
以下例子主要对图像进行锐化。基于拉普拉斯算子<后面讨论>。这幅图像的边缘部分将得到放大,细节部分将更加的锐利。计算方式为:
sharpened_pixel = 5*current – left – right –up – down.
Code:
[cpp] view plaincopyprint?
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void sharpen(const Mat &image, Mat &result)
{
const int nChannels = image.channels();
for(int j = 1; j < image.rows - 1; j++)
{
const uchar *previous = image.ptr<const uchar>(j-1); // 上一行
const uchar *current = image.ptr<const uchar>(j); // 此行
const uchar *next = image.ptr<const uchar>(j+1); //下一行
uchar *output = result.ptr<uchar>(j);
for(int i = 1; i < image.cols - 1; i++)
{
if(image.channels() == 1) // 黑白图像
{
output[i] = saturate_cast<uchar>(5*current[i] - current[i-1] - current[i+1] - previous[i] - next[i]);
}
else if(image.channels() == 3) // 彩色图像
{
*(output + i*image.elemSize()) =saturate_cast<uchar>(5 * *(current + i*image.elemSize()) - *(current + (i-1)*image.elemSize())- *(current + (i+1)*image.elemSize()) - *(previous + i*image.elemSize())- *(next + i*image.elemSize()));
*(output + i*image.elemSize() + 1) = saturate_cast<uchar>(5 * *(current + i*image.elemSize() + 1) - *(current + (i-1)*image.elemSize() + 1)- *(current + (i+1)*image.elemSize() + 1) - *(previous + i*image.elemSize() + 1)- *(next + i*image.elemSize() + 1));
*(output + i*image.elemSize() + 2) = saturate_cast<uchar>(5 * *(current + i*image.elemSize() + 2) - *(current + (i-1)*image.elemSize() + 2)- *(current + (i+1)*image.elemSize() + 2) - *(previous + i*image.elemSize() + 2)- *(next + i*image.elemSize() + 2));
}
}
/*for(int i = nChannels; i < nChannels * (image.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,0,0));
result.col(result.cols - 1).setTo(Scalar(0));
return;
}
void sharpen2D(const Mat &image, Mat &result)
{
Mat kernel(3, 3, CV_32FC1, Scalar(0));
kernel.at<float>(1,1) = 5.0;
kernel.at<float>(0, 1) = -1.0;
kernel.at<float>(1, 0) = -1.0;
kernel.at<float>(1, 2) = -1.0;
kernel.at<float>(2,1) = -1.0;
/*也可以这样定义,这是opencv中简单初始化只有几个像素的mat类型*/
//Mat kernel = (Mat_<float>(3,3) << 0, -1.0, 0.0, -1.0, 5.0, -1.0, 0, -1.0, 0);
filter2D(image, result,image.depth(), kernel);
return;
}
int main()
{
Mat image = imread("F:\\huangrong.jpg", 0);
Mat result;
cout << image.depth() << endl;
result.create(Size(image.cols, image.rows), image.type());
sharpen(image, result);
//sharpen2D(image, result);
namedWindow("image");
imshow("image", image);
namedWindow("result");
imshow("result", result);
waitKey(0);
return 0;
}
Explaination:
(1) saturate_cast<uchar>主要是类型检查来保证转换的安全性。如对于计算得到的结果不在0~255范围内,则进行截断。通常做法是将负值截断为0,将大于255的截断为255.
(2) Matkernel = (Mat_<float>(3,3) << 0, -1.0, 0.0, -1.0, 5.0, -1.0, 0,-1.0, 0);
//opencv中简单初始化只有几个像素的mat类型
(3) result.row(0).setTo(Scalar(0));
//可以将矩阵的第0行所有像素全部设置为0
(4)opencv中自带了filter2D这个函数可以进行领域操作,其结果是一样的,在某些情况下回更高效点。
Result:
二:算术操作
Code:
[cpp] view plaincopyprint?
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat addWeight(Mat &image, Mat &image2)
{
Mat result;
//addWeighted(image, 0.5, image2, 0.5, 0.0, result);
result = image*0.5 + image2*0.5 + 0.3; // 大多数算术函数在opencv2中都有对应的重载操作符
return result;
}
Mat addWeight2(Mat &image, Mat &image3)
{
Mat result;
vector<Mat> planes;
split(image, planes); /// 将一个彩色三通道图像分解为三个单通道图像
planes[0] += image3;
merge(planes, result); /// 将三个单通道图像合并为一个彩色三通道图像
return result;
}
int main()
{
Mat image = imread("F:\\huangrong.jpg", 1);
if(!image.data){
cout << "fail to load image" << endl;
return 0;
}
Mat image2 =Mat::zeros(image.rows, image.cols, image.type());
//Mat image2(image.rows, image.cols, image.ty
展开阅读全文