操作图片
输入输出
加载图片文件
Mat img = imread(filename);
如果读取的文件是jpg格式的文件,那么默认就会为此文件创建一个3通道的图像.如果你仅仅是加载一个灰度图像可使用:
Mat img = imread(filename, IMREAD_GRAYSCALE);
注意
图片的文件格式由图片文件的文件头决定即图片文件的前几个字节所决定,也可以理解为文件的扩展名.保存文件.
imwrite(filename, img);
使用cv::imdecode和cv::imencode是从内存读写图片,而非从文件进行读写图片.
图片的基本操作
访问像素的值
为了得到像素的值,你首先必须知道图像的类型以及颜色通道的数量.下面是一个单通道的灰度图像(类型是8UC1)访问坐标的例子
Scalar intensity = img.at<uchar>(y, x);
intensity.val[0]的值的范围是0-255.需要注意下x,y的顺序.因为在OpenCV中,图像用和矩阵相同的结构来表示,所以我们对这两种情况都使用相同的约定–行(y)索引[从0开始]在前,列(x)索引[从0开始]在后.另外你可以使用下面的C符号
//typedef Scalar_<double> cv::Scalar
Scalar intensity = img.at<uchar>(Point(x, y));
现在我们来考虑一个BGR(imread返回的默认格式)颜色排序的3通道图像:
//typedef Vec<double, 3> cv::Vec3d
Vec3b intensity = img.at<Vec3b>(y, x);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
你可以对浮点图像使用相同的方法(例如你可在3通道的图像上运行Sobel得到图像)
Vec3f intensity = img.at<Vec3f>(y, x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
同样的方法也可用来改变像素的值
img.at<uchar>(y, x) = 128;
OpenCV中有一些函数,特别是calib3d模块的函数,例如cv::projectPoints,它们以Mat的形式接受2D或3D的点阵数组.矩阵应该有确定的列,且每行对应一个点,矩阵的类型应该是32FC2,32FC3,这样的矩阵可以容易的由std::vector构造
vector<Point2f> points;
//... fill the array
Mat pointsMat = Mat(points);
使用同样的Mat::at()方法可以用来访问矩阵中的每一个点.
Point2f point = pointsMat.at<Point2f>(i, 0);
内存管理和引用计数
Mat是一个用于保存矩阵/图像特征(行,列,数据类型)和数据指针的结构体.所以没有什么可以阻止我们有有多个描述同一个图像的Mat实例.Mat还保持了一个引用计数,此引用计数告诉我们当Mat的一个特定实例被销毁的时候是否需要重新分配数据.下面是创建两个矩阵而不是复制两份数据的例子
//typedef Point3_<float> cv::Point3f
std::vector<Point3f> points;
// .. fill the array
Mat pointsMat = Mat(points).reshape(1);
上述代码我们得到一个32FC1的3列矩阵,而非一个32FC3的一列矩阵.pointsMat使用来自point的数据,并且在销毁时不会释放内存.在这个特定例子中,开放者必须确保points的声明周期要比pointsMat的声明周期更长.如果我们需要赋值数据的时候可以使用cv::Mat::copyTo()或者cv::Mat::clone():
Mat img = imread("image.jpg");
Mat img1 = img.clone();
和C API相反在C API中开发者必须创建一个输出图像,并为每个函数提供一个空的输出Mat.C++ API的每个实现均调用Mat::create()来创建目标矩阵.如果矩阵为空,则create()为其分配数据,如果不为空,并且矩阵拥有正确的大小和类型,则此方法不执行任何操作.但是如果size或type和输入参数不同,则释放数据,并重新分配新的数据.
Mat img = imread("image.jpg");
Mat sobelx;
Sobel(img, sobelx, CV_32F, 1, 0);
基本操作
在矩阵上还定义了许多方便的运算符.
从现有的灰度图生成黑色图
img = Scalar(0);
设置ROI感兴趣区域
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
Mat转换为C数据结构
Mat img = imread("image.jpg");
IplImage img1 = cvIplImage(img);
CvMat m = cvMat(img);
需要注意的是转换操作也并未复制数据
彩转灰
Mat img = imread("image.jpg"); // loading a 8UC3 image
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
8UC1转32FC1
src.convertTo(dst, CV_32F);
可视化图像
在开发过程中查看算法的中间结果是很重要的,OpenCV提供了一种方便的图像可视化方法.使用如下方式可以显示8U类型的图像
Mat img = imread("image.jpg");
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", img);
waitKey();
调用waitKey()启动一个消息传递周期,等待"image"窗口中的按键操作,一个32F的图像转换为8U
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx, CV_32F, 1, 0);
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //find minimum and maximum intensities
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();
注意 cv::namedWidow()不是必须的,因为后面紧跟着cv::imshow().当然它可用来改变窗口属性,或使用cv::createTrackBar.
相关函数说明
#include <opencv2/imgproc.hpp>
void cv::Sobel(InputArray src,
OutputArray dst,
int ddepth,
int dx,
int dy,
int ksize = 3,
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
);
使用扩展Sobel算子计算第一,第二,第三或混合图像的导数.
在某一种情况外的所有情况中国,都使用ksize x ksize的可分离Kernel去计算导数.
当ksize =1时,使用3x1或1x3的Kernel(即不做高斯平滑).
ksize=1只能用于计算第一个或第二个x或y的导数.
哈有一个特殊值,ksize = CV_SCHARR(-1),它对应于3x3的Scharr过滤器.它能给出比3x3的Sobel更准确的结果.Scharr是:
∣
3
0
3
10
0
10
−
3
0
3
∣
\left|\begin{matrix} 3 & 0 & 3 \\ 10 & 0 & 10 \\ -3 & 0 & 3 \end{matrix} \right|
∣∣∣∣∣∣310−30003103∣∣∣∣∣∣
求x阶导数或y阶导数的转置.
下面的函数通过将图像与适当的Kernel进行卷积计算图像的导数:
d
s
t
=
δ
x
o
r
d
e
r
+
y
o
r
d
e
r
s
r
c
δ
x
x
o
r
d
e
δ
y
y
o
r
d
e
dst=\frac{\delta^{xorder+yorder}src}{\delta_x^{xorde}\delta_y^{yorde}}
dst=δxxordeδyyordeδxorder+yordersrc
Sobel算子结合了高斯平滑和微分,对噪声有一定的抵抗能力.最常见的使用是(xorder=1,yorder=0,ksize=3)或者(xorder=0,yorder=1,ksize=3)调用函数来计算x或y图像的第一个导数.
第1种情况对应的Kernel是:
∣
−
1
0
1
−
2
0
2
−
1
0
1
∣
\left|\begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right|
∣∣∣∣∣∣−1−2−1000121∣∣∣∣∣∣
第2种情况对应的Kernel是:
∣
−
1
−
2
−
1
0
0
0
1
2
1
∣
\left|\begin{matrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{matrix} \right|
∣∣∣∣∣∣−101−202−101∣∣∣∣∣∣
参数说明
src:输入图片
dst:与输入图片具有相同大小以及通道数的输出图像
ddepth:输出图像深度,
dx:x方向导数
dy:y方向导数
ksize:扩展Sobel算子Kernel的大小,必须是1,3,5,7
scale:可选用于计算导数的比例因子;默认情况下,不用扩展
delta:可选默认增量值,在将结果存储到dst之前添加到结果中的增量值.
borderType:像素向外扩展方法.参考BorderTypes.不支持BORDER_WRAP.
相关原理介绍
#include <opencv2/imgproc.hpp>
void cv::GaussianBlur (InputArray src,
OutputArray dst,
Size ksize,
double sigmaX,
double sigmaY = 0,
int borderType = BORDER_DEFAULT
)
使用高斯滤波器模糊图像.该函数将原图像与指定的高斯Kernel进行卷积,支持In-Place滤波.
参数说明
src:输入图像,图像可以有任意数量的通道,因为这些通道是单独处理的.但是深度应该是CV_8U,CV_16U,CV_16S,CV_32F,CV_64F.
dst:与输入图像拥有相同大小和类型的输出图像
ksize:高斯Kernel的大小.ksize.width和ksize.height可以不同,但必须都是正奇数.或者它们可以是0
sigmaX:X方向的高斯Kernel标准差.
sigmaY:Y方向的高斯Kernel标准差.如果sigmaY=0,则设置sigmaY=sigmaX,如果sigmaY=sigmaX=0,则从ksize.width和ksize.height自动计算两者的值.为了完全控制结果,而不考虑将来可能对所有这些语义的修改,建议指定所有的ksize.sigmaX,sigmaY.
borderType:像素向外扩展方法.参考BorderTypes.不支持BORDER_WRAP.
#include <opencv2/imgproc.hpp>
void cv::cvtColor ( InputArray src,
OutputArray dst,
int code,
int dstCn = 0
)
颜色空间转换.此函数将输入图像从一个颜色空间转到另外的颜色空间.在从RGB颜色空间转换的时候,通道的顺序应该明确指定是(RGB或者BGR).需要注意的是OpenCV的默认颜色格式通常被称为RGB但是实际上是BGR,即字节序做了颠倒.
R,G,B通道值的常见范围是:
- CV_8U:0-255
- CV_16U:0-65535
- CV_32F:0-1
在线性变化的时候,数据的范围并不是很重要.但是在非线性变化的情况下,为了得到正确的结果,需要将输入的RGB图像归一化到合适的值的范围,例如RGB->LUV变换.例如一个32位浮点图像直接变换为8位图像而不进行任何缩放,那么它的取值范围是在0-255而非0-1之间.所以在调用cvtColor之前,需要先缩小图像.
img *= 1./255;
cvtColor(img, img, COLOR_BGR2Luv);
如果使用cvrColor进行8位图像的转换,就会丢失一部分信息.对于许多应用程序,丢失信息是很不容易看到的,但是建议在程序中使用32位图像.如果转换图像有alpha通道,它的值将设置为将设置为通道范围的最大值.
参数说明
src:输入图像
dst:与输入图像拥有相同大小和类型的输出图像
code:颜色空间转换码: 可参考ColorConversionCodes
dstCn:目标图像中的颜色通道个数,如果参数为0,则从src和code中自动推导出目标颜色的通道数.
#include <opencv2/core.hpp>
void cv::convertScaleAbs( InputArray src,
OutputArray dst,
double alpha = 1,
double beta = 0
)
缩放并计算绝对值,并将结果转换为8位数据.对输入数组的每个元素,convertScaleAbs函数按顺序执行三个操作:缩放,取绝对值,转换为无符号的8位类型:
对于多通道数组.该函数独立处理每个通道.当输出不是8位数据的时候,可以使用Mat::convertTo()转换后使用结果来模拟该操作
Mat_<float> A(30,30);
randu(A, Scalar(-100), Scalar(100));
Mat_<float> B = A*5 + 3;
B = abs(B);
// Mat_<float> B = abs(A*5+3) will also do the job,
// but it will allocate a temporary matrix
####参数说明
src:输入数组
dst:输出数组
alpha:可选比例因子
beta:可选的添加到缩放值的增量值.
#include <opencv2/core.hpp>
void cv::addWeighted ( InputArray src1,
double alpha,
InputArray src2,
double beta,
double gamma,
OutputArray dst,
int dtype = -1
)
计算两个数组的加权合.计算方式如下:
d
s
t
(
I
)
=
s
a
t
u
r
a
t
e
(
s
r
c
1
(
I
)
∗
a
l
p
h
a
+
s
r
c
2
(
I
)
∗
b
e
t
a
+
g
a
m
m
a
)
dst(I) = saturate(src1(I) * alpha +src2(I) * beta + gamma)
dst(I)=saturate(src1(I)∗alpha+src2(I)∗beta+gamma)
I是数组元素的多维索引.对于多通道矩阵,每个通道都是独立处理的.此函数可用一个矩阵表达式替换:
注意:当输出数组是数据深度是CV_32S时,色彩饱和度是不使用的[Saturation is not applied when the output array has the depth CV_32S].在溢出的情况下,您设置或得到一个不正确的结果.
####参数说明
src1:第1个输入数组
alpha:第1个数组的权
src2:第2个输入数组,与src1的大小,数据类型和通道一致
beta:第2个数组的权
gamma:追加到每个和的标量
dst:具有相同大小,数据类型,颜色通道数的输出数组
dtype:可选的输出数组的深度.当两个输入数组具有相同的深度时,dtype设置为-1.相当于src1.depth().
sobel边缘检测源码
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
Mat image,src, src_gray;
Mat grad;
const String window_name = "Sobel Demo - Simple Edge Detector";
int ksize = 1;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
String imageName = "../dijia.jpg";
// As usual we load our source image (src)
image = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if( image.empty() ){
printf("Error opening image: %s\n", imageName.c_str());
return EXIT_FAILURE;
}
for (;;){
GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);///高斯模糊输出图像存储在image 为了去除噪声
cvtColor(src, src_gray, COLOR_BGR2GRAY);///将原图转换为灰度图像存储在src_gray
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);///计算x方向导数矩阵
Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);///计算y方向导数矩阵
// converting back to CV_8U
convertScaleAbs(grad_x, abs_grad_x);///依据grad_x进行缩放,计算绝对值,并将结果转换为8位数据存放到abs_grad_x
convertScaleAbs(grad_y, abs_grad_y);///依据grad_y进行缩放,计算绝对值,并将结果转换为8位数据存放到abs_grad_y
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);//对缩放去绝对值后的结果进行加权平均并存储到grad/
imshow(window_name, grad);///显示最终图片
char key = (char)waitKey(0);
if(key == 27)
{
return EXIT_SUCCESS;
}
if (key == 'k' || key == 'K')
{
ksize = ksize < 30 ? ksize+2 : -1;
}
if (key == 's' || key == 'S')
{
scale++;
}
if (key == 'd' || key == 'D')
{
delta++;
}
if (key == 'r' || key == 'R')
{
scale = 1;
ksize = -1;
delta = 0;
}
}
return EXIT_SUCCESS;
}
备注本来以为凭着几年的软件开发经验可以搞下OpenCV了,但是实践看来虽然代码都看得懂.但是深层的原理还是不甚了解呀.有时间还是需要重新找补下丢下的线性代数和高等数学方面的知识了.如有不对或者理解不到位之处,希望大家踊跃指出,共同学习