涉及API:
cv::imread(); //读取
cv::imshow(); //显示
cv::cvtColor(); //修改
cv::imwrite(); //保存
扩展图像窗口创建API:cv::namedWindow();
cv::namedWindow需要两个参数,第一个参数是窗口名称,第二个参数是关于窗口操作的关键字(包含:WINDOW_AUTOSIZE会根据图像大小自动设置窗口大小并且生成的窗口大小不能修改;WINDOW_NORMAL此关键字一般使用在跟QT集成以后的程序中,表示允许修改窗口大小)
cv::imread:
参数两个,第一个参数是图像存储的绝对路径,第二个参数读取图像类型(包含:IMREAD_UNCANGED表示加载原图;IMREAD_GRAYSCALE表示将图像作为灰度图像加载进来;IMREAD_COLOR表示原图作为RGB图像加载进来)
cv::imshow:
两个参数,第一个参数是图像窗口名称(可以自动创建),第二个参数是Mat对象名
cv::cvtColor:
三个参数,第一个是需更改的Mat对象名,第二个是用于保存更改后的Mat对象名,第三个参数是修改使用的源和目标色彩空间(如:COLOR_BGR2GRAY表示修改成灰度图像)
cv::imwrite:
使用时包含两个参数,第一个参数是保存图像的绝对路径,第二个参数是需要保存的Mat对象名
获取图像像素指针:
CV_Assert(image.depth()==CV_8U)
Mat.ptr(int i = 0)获取像素矩阵的指针,其中索引 i 表示第几行,从0开始计行数
获取当前行指针语句:const uchar* current = image.ptr
获取当前像素点P(row,col)的像素值语句:p(row,col) = current[col];
像素范围处理saturate_cast
(重要函数)
saturate_cast(100),返回100
说明:此函数的功能是确保RGB值的范围在0~255之间
什么是图像掩膜操作,掩膜操作实现的是图像对比度调整
opencv提供的掩膜操作(对比度提高)API:filter2D
定义掩膜矩阵:
Mat kernel = (Mat_<char>(3,3)<< 0,-1,0,-1,5,-1,0,-1,0);
API调用举例:filter2D(img, dst, img.depth(), kernel);
说明:第一个参数是操作对象名,第二个参数保存操作后对象名,第三个参数图像深度(使用depth()函数获取了原图像深度),第三个参数是掩膜方法(对应掩膜矩阵)
如何初始化一个零时Mat对象用于存储原图像?
代码:Mat dst = Mat::zeros(img.size(), img.type()); //zeros方法代表创建RGB为0的纯黑图像,大小和类型与原图像相同
拓展:执行时间的显示
代码:
double t = getTickCount();
/* 代码部分 */
double time = (getTickCount() - t)/getTickFrequency();
// cout << "执行时间: " << time << endl;
Mat对象构造函数:
Mat()
Mat(int rows, int cols, int type)
Mat(Size size, int type)
Mat(int rows, int cols, int type, const Scaler &s)
// 说明:前两个参数分别表示行和列,第三个参数是类型参数(比如CV_8UC3中8表示每个通道占8位,U表示无符号,C表示Char类型,3表示三个通道数),第四个参数是向量表示初始化每个像素值为多少,向量长度对应通道数目一致。
Mat(Size size, int type, const Scaler &s) //Scaler()用来给像素赋值
Mat(int ndims, const int *sizes, int type)
Mat(int ndims, const int *sizes, int type, const Scaler &s)
说明:拷贝构造函数只会赋值对象头部,使用API–>demo = mat.clone() or mat.copyTo(demo)
才能进行完全复制(包括数据部分)
常用方法:
void copyTo(Mat mat);
void convertTo(Mat dst, int type);
Mat clone();
int channels();
int depth();
bool empty();
uchar* ptr(i = 0); //备注:查资料详学(读取像素值)
cv::Mat::create(size, type) //create方法创建对象(可指定对象尺寸大小)
两种用法:
M.create(img.size(), img.type());
M.create(4,3,CV_8UC2); M = Scaler(123,123);
定义小数组:(掩膜运用)
Mat kernel = (Mat_
初始化全0图像有多种方法,其中比较特殊的是Mat::zero(size, type);
用法:
Mat m = Mat::zero(img.size(), img.type());
Mat m = Mat::zero(2, 2, CV_8UC1);
拓展:Mat::eye(……)
方法,初始化对角线为一的图像矩阵。
读取像素:
· 读取一个gray像素点的像素值(CV_8UC1)
Scalar intensity = img.at
· 读取一个RGB像素点的像素值
Vec3f intensity = img.at<Vec3f>(row, col);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
for(int row = o; row < img.rows; row++)
{
for(int col = 0; col < img.cols; col++)
{
int b = img.at<Vec3b>(row, col)[0];
int g = img.at<Vec3b>(row, col)[1];
int r = img.at<Vec3b>(row, col)[2];
}
}
说明:Vec3b是一种数据结构,放置BGR像素点,3b表示3bit读取,也可以用Vec3f,3f表示以float类型读取,如第一种读取方法。
修改像素:
· 灰度图像
img.at
· RGB图像
img.at
img.at
img.at
· 空白图
img = Scalar(100); //将每个像素点赋值为100
· ROI选择
Rect r(10, 10, 100, 100);
Mat smallimg = img(r);
Vec3b与Vec3f
· Vec3b对应三通道顺序blue、green、red的uchar类型数据
· Vec3f对应三通道float类型数据
· 把CV_8UC1转换到CV32F1实现如下:
img.convertTo(dst, CV_32F1); //使用API-->convertTo(dst, type);
· 理论-线性混合操作
g(x) = (1-a)f0(x)+af1(x)
说明:f0(x)表示一个图像,f1(x)表示另一个图像,其中a的取值范围为0~1之间,g(x)表示混合后得到的图像(注意:对图像每个像素的操作)
相关API
cv::addWeighted(inputArray src1, //参数1:输入图像Mat - src1
double alpha, //参数2:输入图像src1的alpha值(alpha表示表达式中的a)
inputArray src2, //参数3:输入图像Mat - src2
double beta, //参数4:输入图像src2的alpha值
double gamma, //参数5:gamma值(校验值,使其得到正常图像)
OutputArray dst, //参数6:输出混合图像
int dtpye = -1 //dtpye默认不用带入
)
注意:两张图像大小和类型必须一致才可以使用此API混合
dst(I) = saturate(src1(I) * alpha +src2(I) * beta + gamma);
拓展API:
add(img, dst, dst1); //直接叠加两个图像像素
multiply(img, dst, dst1, 1.0); //两个图像像素相乘
图像变换可以看作像素变换(点操作)和领域操作(区域操作),调整图像亮度和对比度属于像素变换
g(i,j) = af(i,j) + β (其中a>0,β是增益变量)
再次回顾重要API:
//像素范围处理函数
saturate_cast
//图像数据类型转换
img.convertTo(dst, CV_32F);
说明:如果图像默认bit类型,我们把数据转换成浮点型,提高图像精度可以使处理效果提高。
· 使用cv::Point
与cv::Scalar
Point表示2D平面上一个点,坐标(x,y)
如下:
Point p;
p.x = 10;
p.y = 8;
Point p1 = Point(x, y );
Scalar表示四个元素的向量
Scalar(a, b, c); //a = Blue, b = green, c = Red表示BGR三个通道
· 绘制线、矩形、圆、椭圆等基本几何形状
API使用:
线–> cv::line(LINE_4\LINE_8\LINE_AA) //参数表示绘制线的类型,LINE_AA表示反锯齿
椭圆–> cv::ellipse
椭圆API说明:使用语句示例–>
ellipse(img, Point(img.rows/2, img.cols/2), Size(img.rows/5, img.cols/6), 90, 0, 360, color, 2, 8);
/* Point表示椭圆中心坐标,size表示椭圆尺寸,其中两个参数表示长短轴,angle = 90表示顺时针方向旋转角,startAngle = 0表示绘制的起始角度,endAngle = 360表示绘制的终止角度。*/
矩形–> cv::rectangle //五个参数,第一个参数是Mat对象,第二个参数矩形类型,第三个参数颜色,第四个参数线宽(默认1),第五个参数线的类型(默认LINE_8)
圆–> cv::circle
填充–> cv::fillPoly
fillPoly各参数说明:用法–>
Point pts[1][5] = { Point(100,100), Point(100,200), Point(200,200), Point(200,100), Point(100,100) };
const Point* ppts[] = { pts[0] };
int npt[] = { 5 };
fillPoly(img, ppts, npt, 1, Scalar(255,0,255), 8);
//ppts表示多边形各顶点集合(静态点对象指针数组),npt表示多边形顶点个数, //ncontours = 1表示填充个数
对象:
Rect rect = Rect(x, y, w_len, h_len); //后两个参数分别是宽高
· 随机生成与绘制文本
绘制文本API:cv::putText(Mat&, string, Point, int_fontFace, double_fontScale, Scalar, thickness, lineType, bottomLeftOrigin = false);
代码示例:
putText(img, "Hello OpenCV", Point(100,100), CV_FONT_HERSHEY_COMPLEX, 1.0, Scalar(0,0,255), 1, 8);
说明:int_fontFace表示字体类型,double_fontScale表示字体缩放比例,bottomLeftOrigin默认为FALSE不用管。
随机生成图像(以画线为例)
代码如下:
void RandomLineDemo(Mat& img)
{
RNG rng(12345);
Point pt1, pt2;
Mat dst = Mat::zeros(img.size(), img.type());
namedWindow("test7", CV_WINDOW_AUTOSIZE);
for (int i = 0; i < 10000; i++)
{
pt1.x = rng.uniform(0, img.cols);
pt2.x = rng.uniform(0, img.cols);
pt1.y = rng.uniform(0, img.rows);
pt2.y = rng.uniform(0, img.rows);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
if (waitKey(50) > 0)
break;
imshow("test7", dst);
line(dst, pt1, pt2, color);
}
}
说明:RNG是opencv中的随机数类,构造函数指明随机数范围或种子个数,使用uniform(正态分布随机数)方法指定随机数范围,同样的也可以使用gaussian(double sigma)方法生成高斯随机数
· 图像的模糊原理
· 相关API
均值模糊:
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1)); //Point表示中心像素在哪里,(-1, -1)表示默认中心像素
高斯滤波:
GaussianBlur(Mat src, Mat dst, Size(11, 11), sigmax, sigmay); //sigmax, sigmay是用于调节正态分布图像的参数
注意:其Size(x, y)中x和y必须是正数而且是奇数,size表示窗口大小
· 中值滤波
API:medianBlur (Mat src, Mat dst, ksize)
注意:中值模糊的ksize大小必须是大于1而且为奇数 --> ksize表示卷积核大小
· 双边滤波
API:bilateralFilter (src, dst, d=15, 150, 3)
说明:d=15为计算半径,半径之内的像素都会被纳入计算,如果该参数提供-1则会根据sigma space参数取计算半径
150表示sigma color,决定多少差值之内像素会被计算
3表示sigma space如果d值大于0则声明无效
· 膨胀
说明:膨胀操作跟卷积操作类似,假设有图像A和机构元素B,结构元素B在A上移动,其中B定义其中心为锚点,计算B覆盖下A的最大像素值用来替换锚点像素其中B作为结构体可以是任意形状。而腐蚀跟膨胀操作类似,唯一不同的是以最小值替换锚点重叠下图像的像素值。
相关API:
· getStructuringElement(int shape, Size ksize, Point anchor) //获取结构形状
说明:三个参数分别代表形状(MORPH_RECT \ MORPH_CROSS \ MORPH_ELLIPSE)、大小(要求奇数)、锚点(默认是Point(-1,-1)意思就是中心像素)
· dilate(src, dst, kernel) //膨胀
· erode(src, dst, kernel) //腐蚀
拓展:
动态调整结构元素大小 (GUI函数)
TrackBar -> createTrackbar(constString & trackbarname,
const String winName,
int* value,
int count,
Trackbarcallback func,
void* userdata = 0)
//其中最重要的是callback函数功能,如果设置为NULL就是说只有值update,但是不会调用callback的函数。
· 开操作 - open
说明:图像先腐蚀后膨胀的操作即称为图像的开操作,图像开操作可以去掉小的对象。
· 闭操作 - close
说明: 先膨胀后腐蚀称之为闭操作,可以讲大面积图像中小的缺口给填充。
· 形态学梯度 - Morphological Gradient
说明:图像膨胀后减去原图的腐蚀图像,此种方法又称基本梯度,得到的图像具有梯度效果。
· 顶帽 - top hat
说明:顶帽是原图像与开操作之间的差值图像
· 黑帽 - black hat
说明:黑帽是闭操作图像与源图像的差值图像
API:morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel);
参数说明:
- int OP --> CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT/ CV_MOP_TOPHAT/ CV_MOP_BLACKHAT (表示形态学操作类型)
- Mat kernel --> 结构元素(选取大小取决于去掉的对象大小)
- int Iteration=1 --> 迭代次数,默认为1
使用实例(开操作):
Mat kernal = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_OPEN, kernal);
imshow("dst", dst);
waitKey(0);
· 原理方法
图像形态学操作的时候,可以通过自定义的结构元素实现结构元素对输入图像一些对象敏感、另外一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出。
通过使用两个最基本的形态学操作:膨胀与腐蚀。使用不同的结构元素实现对输入图像的操作并得到想要的结果。
知识回顾:
· 结构元素
膨胀与腐蚀过程是可以使用任意的结构元素,常见的形状:矩形、圆、直线、磁盘形状、砖石形状等各种自定义形状。
· 提取步骤
1 - 输入图像
2 - 灰度变换
3 - 二值化 --> adaptiveThreshold
4 - 定义结构元素
5 - 开操作提取水平与垂直线
相关API - adaptiveThreshold(src, dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C);
参数说明:
double maxValue - 输入图像最大灰度值
int adaptiveMethod - 自适应阈值方法(ADAPTIVE_THRESH_MEAN_C / ADAPTIVE_THRESH_GAUSSIAN_C)
int thresholdType - 阈值类型(常用 THRESH_BINARY)
int blockSize - 子块大小
double C - 常量,可为正数、负数、0
结构元素定义实例代码:
Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
Mat wline = getStructruingElement(MORPH_RECT, Size(1, src.row . 16), Point(-1, -1));
拓展API:bitwist_not(src, src);
--> 图像二进制数据“非”操作
· 图像金字塔概念说明
我们在图像处理中常常会调整图像大下,最常见的就是放大和缩小,尽管几何变换也可以实现图像的放大和缩小,但是这里所说的
大小调整是指图像金字塔从下到上分辨率的缩小。一个图像金字塔是一系列图像组成,最底下一张是图像尺寸最大,最上方图像尺
寸最小,从空间上由上向下看就像古埃及金字塔。
图像金字塔分为高斯金字塔和拉普拉斯金字塔,高斯金字塔用来对图像进行降采样,拉普拉斯金字塔用来重建一张图片根据它的上
层降采样图片
高斯金字塔:
高斯金字塔是从底向上,逐层采样得到的
降采样之后图像大小是原图像 M × N 的 M/2 × N/2,就是对原图像删除偶数行与列,即得到采样后的上层图像
高斯金字塔的生成过程分为两步:
1 - 对当前层进行高斯模糊
2 - 删除当前层的偶数行与列
即可得到上一层图像,这样上一层和下一层相比,都只有它大小的1/4
{1, 4, 6,4,1;
4,16,24,16,4;
1/16 × 6,24,36,24,6;
4,16,24,16,4;
1, 4, 6, 4,1;}
高斯不同(DOG):
定义:把同一张图像在不同的参数下做高斯模糊之后的结果相减,得到的输出图像称之为高斯不同。
高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常使用
代码示例:
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, g1_dst, Size(3, 3), 0, 0);
GaussianBlur(g1_dst, g2_dst, Size(3, 3), 0, 0);
subtract(g1_dst, g2_dst, DOGimage, Mat());
normalize(DOGimage, DOGimage, 255, 0, NORM_MINMAX); // 还原灰度范围
imshow("DOG image", DOGimage);
waitKey(0);
相关API:
· 上采用 --> cv::pyrUp(Mat src, Mat dst, Size(src.cols*2, src.rows*2))
效果:生成的图像是原图在宽和高各放大两倍
· 降采样 --> cv::pyrDown(Mat src, Mat dst, Size(src.col/2, src.rows/2))
效果:生成的图像是原图在宽与高各缩小1/2
阈值操作:二值化、反二值化、截断、阈值取零、阈值反取零
API–>cv::threshold(img, dst, thresh, max_value, type);
说明:type参数表示阈值操作类型,可填写cv::THRESH_BINARY, cv::THRESH_BINARY_INV, cv::THRESH_TRUNC, cv::THRESH_TOZERO, cv::THRESH_TOZERO_TNV等。
·OpenCV中常用的边缘填充函数为copyMakeBorder();
函数原型:void copyMakeBorder(const Mat &src, Mat& dst, int top, int bottom, int left, int right, int borderType, const Scalar &value=Scalar());
功能:扩充src的边缘,将图像变大,然后以各种外插方式自动填充图像边界,这个函数实际上调用了函数cv::borderInterpolate,这个函数最重要的功能就是为了处理边界,比如均值滤波或者中值滤波中,使用copyMakeBorder将原图稍微放大,然后我们就可以处理边界的情况。
参数说明:
src,dst:原图与目标图像
top,bottom,left,right分别表示在原图四周扩充边缘的大小
borderType:扩充边缘的类型,就是外插的类型,OpenCV中给出以下几种方式
BORDER_REPLICATE
BORDER_REFLECT
BORDER_REFLECT_101
BORDER_WRAP
BORDER_CONSTANT
BORDER_REPLICATE:边缘像素复制法
BORDER_REFLECT_101:对称法,以最边缘像素为轴,对称
BORDER_CONSTANT:常量法
API:cv::Sobel(img, dst, depth, dx, dy); // dx和dy分别表示x方向和y方向上的差分阶数
cv::Laplacian(img, dst, depth);
· Canny算法五步走:
1、高斯模糊 - GaussianBlur
2、灰度转换 - cvtColor
3、计算梯度 - Sobel / Scharr
4、非最大信号抑制
5、高低阈值连接输出二值图像
什么是非最大信号抑制:
图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。
根据图可知,要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。这就是非极大值抑制的工作原理。
注意以下两点:
1)中非最大抑制是回答这样一个问题:“当前的梯度值在梯度方向上是一个局部最大值吗?” 所以,要把当前位置的梯度值与梯度方向上两侧的梯度值进行比较;
2)梯度方向垂直于边缘方向。
但实际上,我们只能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在其中,要得到这两个值就需要对该两个点两端的已知灰度进行线性插值,也即根据图中的g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值,这要用到其梯度方向,这是Canny算法中要求解梯度方向矩阵Thita的原因。
完成非极大值抑制后,会得到一个二值图像,非边缘的点灰度值均为0,可能为边缘的局部灰度极大值点可设置其灰度为128。根据下文的具体测试图像可以看出,这样一个检测结果还是包含了很多由噪声及其他原因造成的假边缘。因此还需要进一步的处理。
高低阈值连接:
· API介绍
cv::Canny(InputArray src, // 8bit输入图像
OutputArray edges, // 输出边缘图像,一般都是二值图像,背景为黑色
double threshold1, // 低阈值,常取高阈值的1/2或者1/3
double threshold2, // 高阈值
int aptertureSize, // Sobel算子size,通常为3×3,取值3
bool L2gradient // 选择true表示是L2归一化方法,否则使用L1方法
)
· 相关代码
#include
#include
#include
using namespace cv;
using std::cout;
using std::endl;
const int t1_value = 50, max_value = 255;
Mat src, dst, gray_src;
void Canny_Demo(int, void*)
{
Mat edge_output;
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
Canny(gray_src, edge_output, t1_value, t1_value*2, 3, false);
dst.create(src.size(), src.type());
src.copyTo(dst, edge_output);
// imshow("output", dst);
imshow("out", edge_output);
}
int main(int argc, char** argv)
{
src = imread("Path");
if(!src.data)
{
cout << "could not load image" << endl;
return -1;
}
imshow("input", src);
cvtColor(src, gray_src, CV_BGR2GRAY);
createTrackbar("Threshold Value", "Result", &t1_value, max_value, Canny_Demo);
Canny_Demo(0, 0);
waitKey(0);
return 0;
}