说明
这篇博客只用来记录目前我已经接触过的API,只涉及用法及效果,不涉及背后算法,具体算法我会在其他的博客中进行介绍。随着逐渐学习,我也会对这篇博客进行动态更新,有些内容缺少的就是我也还没弄懂的。
并不会详细解释API,更适合有一定经验的人查阅。
我也只是个初学者,很多内容都是跟着教程的框架进行学习,如果内容上有错误欢迎大家指正与补充。
基础操作
读入
图片
imread()函数
Mat imread( const String& filename, int flags = IMREAD_COLOR );
第一个参数是图片路径
第二个参数是读入色彩模式
常用的模式:
IMREAD_UNCHANGED 按图片原色彩模式读入
IMREAD_GRAYSCALE 按灰度图读入
IMREAD_COLOR 按BGR彩色读入
样例代码
Mat demo;
demo = imread("F://opencv file/picture.jpg" , IMREAD_UNCHANGED);
视频或摄像头
VideoCapture类
使用样例
VideoCapture cap;
cap.open(0); // cap.open("F:\\opencv file/测试视频.mp4");
//以上等价于 VideoCaputure cap(0);
Mat demo;
while(1){
cap>>demo
imshow("demo",demo);
waitKey(30);
}
先创建一个VideoCapture通过open可以打开摄像头或者视频,摄像头的话是使用摄像头的index号,笔记本摄像头默认为0。视频是使用路径。
然后用>>读入到Mat中进行处理,循环输出。
VideoCapture还有一些常用的方法。
cap.get(CV_CAP_PROP_FRAME_WIDTH) //帧宽度
cap.get(CV_CAP_PROP_FRAME_HEIGHT) //帧高度
cap.get(CV_CAP_PROP_FPS); //帧率 x frames/s
cap.get(CV_CAP_PROP_FRAME_COUNT); //总帧数
输出
图片
imwrite()函数
imwrite( const String& filename, InputArray img,const std::vector<int>& params = std::vector<int>());
第一个参数是输出的文件路径
第二个参数是要输出的图片
视频
VideoWriter类
这部分我还没倒腾明白,之后用到的时候再补齐
颜色
Scalar对象与函数
样例代码
Scalar color = Scalar(255,255,255) //设置色彩为白色
随机数(随机色彩)
RNG类
样例代码
RNG rng((unsigned)time(NULL)); //声明时需要设定一个种子,一般会设置为当前时间,可以保证每次的种子都不同。
int i = rng.uniform(0,255) //uniform(a,b),可以生成[a,b)之间的随机数,返回值与输入的范围值相同,也可以用强制转换得到多种数据类型
像素范围处理
saturate_cast()函数
用于使数值不超过数据类型允许范围
样例代码
uchar r = saturate_cast<uchar>(400) //返回255
uchar r = saturate_cast<uchar>(-100) //返回0
uchar r = saturate_cast<uchar>(100) //返回100
色彩转换
cvtColor()函数
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是转换模式
常用的有:
CV_BGR2GRAY //BGR转灰度
CV_BGR2HSV //BGR转HSV
样例代码
cvtColor(demo,demo,CV_BGR2GRAY); //从BGR彩色图转换为灰度图
形态学操作、直方图、边缘检测等API需要输入单通道的图,就可以将彩色图转换为单通道的灰度图,也可以选择将不同的通道分为不同的图像。
通道分离
将多通道的彩色图像,分为多个单通道的图。
split()函数
void split(const Mat& src, Mat* mvbegin)
第一个参数是输入Mat对象地址
第二个参数是分通道输出的Mat对象数组
样例代码
Mat dst[3]; //声明Mat对象数组
split(demo,dst); //将demo对象的三个通道分别赋给dst数组
要注意分离后的图像都是单通道,直接显示都是灰度图。
通道合并
将多个单通道图像合并为一个多通道的图像
merge()函数
函数原型
void merge(InputArrayOfArrays mv, OutputArray dst);
第一个参数是输入的多个单通道矩阵/图像的阵列,必须有相同深度和尺寸
第二个参数是合并后的矩阵/图像,通道数需要与参数一中的个数一致
样例代码
vector<Mat> channels; //用于储存分离后的通道
split(image_src, channels); //分离通道
merge(channels, image_dst); //合并通道
该函数是上边split()的逆操作,参数也类似。
图像线性混合
addWeighted()函数
函数原型
void addWeighted(InputArray src1, double alpha, InputArray src2,double beta, double gamma, OutputArray dst, int dtype = -1);
第一个参数是输入图像1
第二个参数是图像1所占的比例
第三个参数是输入图像2
第四个参数是图像2所占的比例
第五个参数是偏差值,用0就可以
第六个参数是输出图像
样例代码
addWeighted(image1, 0.4 , image2 , 0.6 , 0 , image_out);
图像线性运算
cvConvertScale()函数
函数原型
cvConvertScale( const CvArr* src, CvArr* dst, double scale ,double shift );
第一个参数是输入矩阵
第二个参数是输出矩阵
第三个参数是乘数因子
第四个参数是加权值
最后结果是输入矩阵中的每个元素 乘以乘数加上加权值 得到输出矩阵
cvConvertScaleAbs()函数
参数与上边的API一致,区别是这个API输出时会取绝对值。
归一化
normalize()函数
函数原型
void normalize(InputArray src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )
第一个参数是输入数组/图像
第二个参数是输出数组/图像
第三个参数是用来规范值或者规范范围,并且是下限。
第四个参数是用来规范范围并且是上限 只对NORM_MINMAX有效
第五个参数是归一化的模式
归一到(min,max)范围内:NORM_MINMAX 最小值为0,最大值为1,其他数根据比例计算
根据切比雪夫距离归一:NORM_INF 最大值为1,其他数除以最大值
根据曼哈顿距离归一:NORM_L1 每个数除以所有数和(绝对值)
根据欧几里得距离归一:NORM_L2 每个数除以所有数平方和的开方
样例代码
vector<double>demo = {10,17,20};
normalize(demo,demo, 1 , 255 ,NORM_L1);
画线与图形
线
line()函数
函数原型
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color,int thickness = 1, int lineType = LINE_8, int shift = 0);
第一个参数是输入的图像
第二个参数是线段起始点
第三个参数是线段结束点
第四个参数是线段颜色
第五个参数是线段宽度
第六个参数是线形
样例代码
line(image , Point(50,50) , Point(100,100) , Scalar(255,0,0) , 3 , LINE_8);
矩形
rectangle()函数
函数原型
rectangle(InputOutputArray img, Point pt1, Point pt2,const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
与上边划线类似,第一个点是矩形左上角,第二个点是矩形右下角
样例代码
rectangle(image , Point(50,50) , Point(100,100) , Scalar(255,0,0) , 3 , LINE_8);
圆形
circle()函数
函数原型
void circle(InputOutputArray img, Point center, int radius, const Scalar& color, int thickness = 1,int lineType = LINE_8, int shift = 0);
第一个参数是输入图像
第二个参数是圆心坐标
第三个参数是半径
第四个参数是颜色
第五个参数是线宽
第六个参数是线形
样例代码
circle(image, Point(50,50) , 20 , Scalar(255,0,0) , 2 , LINE_8);
椭圆
ellipse()函数
函数原型
void ellipse(InputOutputArray img, Point center, Size axes,
double angle, double startAngle, double endAngle, const Scalar& color, int thickness = 1,int lineType = LINE_8, int shift = 0);
第一个参数是输入图像
第二个参数是椭圆中心点
第三个参数是椭圆的大小(姑且可以认为是可以框住椭圆的矩形尺寸,可以以此确定椭圆的长轴与短轴长度)
第四个参数是椭圆沿逆时针方向旋转的角度
第五、六个参数分别是椭圆开始的角度以及结束的角度(用于画带缺口的椭圆线,角度沿长轴顺时针方向)
第七个参数是颜色
第八个参数是线宽
第九个参数是线形
样例代码
ellipse(image , Point(100,100) , Size(30,60) , 30 , 0 , 180 , Scalar(255,0,0) , 2 , LINE_8);
多边形或折线
polylines()函数
支持一次性画多个多边形
函数原型
void polylines(Mat& img, const Point* const* pts, const int* npts,int ncontours, bool isClosed, const Scalar& color,int thickness = 1, int lineType = LINE_8, int shift = 0 );
第一个参数是画布图像
第二个参数是顶点数组,由于该函数支持同时画多个多边形,所以顶点数组是二维数组,且要将每个数组的头地址再装进一个一维数组中,这里的参数只用最后的一维数组
第三个参数是顶点数量,也是为了画多个图形,所以需要用数组的形式储存顶点数量
第四个参数是多边形的数量
第五个参数是折线是否闭合,真或假
后边的就是颜色、线型之类的了
样例代码
Point points[2][3];
points[0][0] = Point(50,50);
points[0][1] = Point(20,50);
points[0][2] = Point(30,40);
points[1][0] = Point(60,50);
points[1][1] = Point(50,80);
points[1][2] = Point(40,30);
const Point* ptr[]={points[0] , points[1]};
int npts[] = {3,3};
polylines(image , ptr , npts , 2 , ture , Scalar(255,0,0) , 2, LINE_8);
fillPoly()函数
该函数参数与polylines基本一致,区别是该函数是实心多边形,polylines只是边框
函数原型
fillPoly(Mat& img, const Point** pts,const int* npts, int ncontours,const Scalar& color, int lineType = LINE_8, int shift = 0, Point offset = Point() );
和上边很相似,基本只需要五个参数,输入图像,顶点数组,顶点数量,图形数量,填充颜色
文字
putText()函数
函数原型
void putText( InputOutputArray img, const String& text, Point org, int fontFace, double fontScale, Scalar color,
int thickness = 1, int lineType = LINE_8,bool bottomLeftOrigin = false );
第一个参数是输入图像
第二个参数是要输出的字符串
第三个参数是文本框左下角的坐标
第四个参数是字体
第五个参数是尺寸
第六个参数是颜色
样例代码
putText(image , "Hello world" , Point(50,50) , FONT_HERSHEY_COMPLEX , 2 , Scalar(255,0,0));
点操作
- at方法
样例代码
uchar value = image.at<uchar>(i,j); //获得单通道图像i行j列像素点的值
image.at<uchar>(i,j) = 255; //单通道图像赋值操作
uchar value = image.at<Vec3b>(i,j)[0]; //取出三通道图像i行j列像素点的第一个通道数值。
Vec3b pixel = image.at<Vec3b>(i,j); //将三通道图像i行j列像素点数组赋给pixel
需要注意对遍历多通道图像时使用像素点数组进行操作时,列数为像素点的列数(及图像列数)。直接获取单通道数值进行操作时,列数为像素点的列数*通道数。
at函数可读性高,但效率不高,不适合用来遍历图像。
- ptr方法
样例代码
const uchar* date_in = image_in.ptr<uchar>(i); //获取图像第i行像素值的指针,设置为常量是为了保证原图不变,保留副本。
uchar* date_out = image_out.ptr<uchar>(i);
date_out[j] = date_in[j]; //复制像素
需要特别注意,这里是将一行的数据装进数组,也就意味着对于通道的图,一行数据一共有图像列数*通道数个。如果要对单一通道进行操作,遍历时每次应该向下移动通道数个数据。
以这种方法遍历,效率更高。
遍历时,单点像素乘以常数可以调整对比度,加减常数可以调整亮度。
卷积操作
卷积核
想要进行卷积运算需要先定义一个卷积核。
Mat kernel = (Mat_<uchar>(3,3) << 0,1,0,1,-4,1,0,1,0);
上式创建的掩膜为:
0 | 1 | 0 |
---|---|---|
1 | -4 | 1 |
0 | 1 | 0 |
该卷积核可以用来提高对比度。又被成为拉普拉斯算子
sobel算子,可以用于检测边缘
检测横向边缘:
-1 | 0 | 1 |
---|---|---|
-2 | 0 | 2 |
-1 | 0 | 1 |
检测纵向边缘
-1 | -2 | -1 |
---|---|---|
0 | 0 | 0 |
1 | 2 | 1 |
卷积
filter2D()函数
函数原型
void filter2D( InputArray src, OutputArray dst, int ddepth,InputArray kernel, Point anchor = Point(-1,-1),double delta = 0, int borderType = BORDER_DEFAULT )
第一个参数是输入图像
第二个参数是输出图像
第三个参数是图像深度, -1 代表与输入图像一致
第四个参数是卷积核
第五个参数是锚点
样例代码
filter2D(image_in,image_out,-1,kernel); //使用kernel为卷积核输出image_out
模糊
均值模糊/均值滤波
blur()函数
函数原型
void blur( InputArray src, OutputArray dst,Size ksize, Point anchor = Point(-1,-1),int borderType = BORDER_DEFAULT );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是均值模糊卷积核的大小,全方向模糊一般取3*3或5*5,也可以去单列或者单行的大小以进行单方向的模糊。要注意这个尺寸要确保有中心点存在。(2*2就不存在绝对的中心点)
第四个参数是锚点,默认位卷积核中心
第五个是边缘处理模式,一般用默认
样例代码
blur(image_in , image_out , Size(3,3));
均值模糊无法保留边缘特征,只能整体模糊。
中值模糊/中值滤波
medianBlur()函数
函数原型
void medianBlur( InputArray src, OutputArray dst, int ksize );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是卷积核的大小,固定是正方形,所以只取数字就可以,9就代表9*9的范围
样例代码
medianBlur(image_in , image_out , 3);
中值滤波很适合去除椒盐噪声。
高斯模糊/高斯滤波
高斯
GaussianBlur()函数
函数原型
void GaussianBlur( InputArray src, OutputArray dst, Size ksize,double sigmaX, double sigmaY = 0,int borderType = BORDER_DEFAULT );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是卷积核的大小
第四个参数是x方向上的标准偏差
第五个参数是y方向上的标准偏差
第六个参数是边缘处理模式
样例代码
GaussianBlur(image_in , image_out , Size(3,3) , 0.5 , 0.5);
这种高斯模糊相较于均值模糊可以保留更多细节,但对边缘的保留一样效果不太好。
双边高斯
bilateralFilter()函数
函数原型
void bilateralFilter( InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace,int borderType = BORDER_DEFAULT );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是过滤器的大小,太大的话会影响速度
第四个参数是色彩空间的sigma,大一点可以提高模糊效果
第五个参数是坐标空间的sigma,小一点可以提高边缘保留效果
样例代码
bilateralFilter(image_in , image_out , 5 , 150 , 3);
模糊效果不错,而且可以保留边缘,对人脸处理时,有类似于磨皮的效果。
形态学操作
结构体
getStructuringElement()函数
函数原型
Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
第一个参数是结构体的形状
预设的形状有:MORPH_RECT 矩形 MORPH_CROSS 十字 MORPH_ELLIPSE 椭圆形
第二个参数是结构体的大小
第三个参数是锚点位置
样例代码
Mat kernel = getStructuringElement(MORPH_RECT , Size(3,3));
运算
morphologyEx()函数
函数原型
void morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1,int borderType = BORDER_CONSTANT,const Scalar& borderValue = morphologyDefaultBorderValue() );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是操作模式
操作模式有:
膨胀:MORPH_DILATE 锚点处像素值取结构体覆盖下的最大值 扩大亮处
腐蚀:MORPH_ERODE 锚点处像素值取结构体覆盖下的最小值 扩大暗处
开运算:MORPH_OPEN 先腐蚀再膨胀 去掉小亮斑
闭运算: MORPH_CLOSE 先膨胀再腐蚀 去掉小暗斑
顶帽: MORPH_TOPHAT 原图像与开运算后的结果求差 可以观察开运算去除了哪些物体
黑帽: MORPH_BLACKHAT 原图像与闭运算后的结果求差 可以观察闭运算的效果
形态学梯度:MORPH_GRADIENT 膨胀后的图减去腐蚀后的图 可以得到图像大致的边缘
第四个参数是结构体
检测直线/字符
通过合理设置结构体,再经过开运算就可以检测直线或者字符
检测水平直线,就设置单行较长的结构体
检测竖直直线,就设置单列较长的结构体
检测较粗字符,可以设置正方形的结构体
阈值
阈值操作
threshold()函数
函数原型
threshold( InputArray src, OutputArray dst,double thresh, double maxval, int type );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是阈值
第四个参数是最大值
第五个参数是操作模式
操作模式有:
二值化:THRESH_BINARY 大于阈值为最大,小于阈值为0
反二值化: THRESH_BINARY_INV 大于阈值为0,小于阈值为最大
截断: THRESH_TRUNC 大于阈值的限制为阈值,小于阈值的保持不变
取零: THRESH_TOZERO 大于阈值的不变,小于阈值的为0
反取零: THRESH_TOZERO_INV 大于阈值的为0,小于阈值的不变
最大类间方差: THRESH_OTSU 通过统计学计算阈值,常用于分离物体与背景
自适应阈值: THRESH_TRIANGLE 根据不同区域自行计算阈值,更多时候用另一个API实现
样例代码
threshold(image_in , image_out , 150 , 255 , THRESH_TOZERO);
adaptiveThreshold()函数
自适应阈值操作
函数原型
void adaptiveThreshold( InputArray src, OutputArray dst,double maxValue, int adaptiveMethod,int thresholdType, int blockSize, double C );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是最大值
第四个参数是自适应类型
平均值:ADAPTIVE_THRESH_MEAN_C 邻域像素平均值减去C作为阈值
高斯值:ADAPTIVE_THRESH_GAUSSIAN_C 邻域像素通过高斯加权平均减去C,sigma值取决与邻域大小
第五个参数是阈值操作模式,只能是THRESH_BINARY或者THRESH_BINARY_INV
第六个参数是计算阈值的邻域大小,3,5,7之类的
第七个参数是偏差值,可以设为0
样例代码
adaptiveThreshold(image_in , image_out , 255 , ADAPTIVE_THRESH_MEAN_C , THRESH_BINARY , 3 , 0);
合理的设置阈值可以分离物体与背景,也可以用于获得物体的大致边缘。
边缘检测
Canny()函数
函数原型
void Canny( InputArray image, OutputArray edges,double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false );
第一个参数是输入图像
第二个参数是输出图像
第三个参数是阈值1 低于这个阈值的点被认为不是边缘
第四个参数是阈值2 高于这个阈值的点被认为是边缘, 处于1与2之间的点会检测是否与边缘点接触,若接触则为边缘点
第五个参数是邻域范围
第六个参数是 是否使用更加精确的计算方法
样例代码
Canny(image_in , image_out , 100 , 200 , 3 , ture);
使用Canny前,要先进行滤波,提高效果
霍夫变换
直线
HoughLinesP()函数
函数原型
void HoughLinesP( InputArray image, OutputArray lines,double rho, double theta, int threshold,double minLineLength = 0, double maxLineGap = 0 );
第一个参数是输入图像
第二个参数是输出的直线两个端点坐标
第三个参数是极坐标中像素的扫描步长
第四个参数是极坐标中角度的扫描步长,一般取 CV_PI/180 也就是一度
第五个参数是阈值,极坐标系中至少要多少个函数相交才会看作直线
第六个参数是直线的最小长度
第七个参数是直线延伸过程中最大的间隔
样例代码
vector<Vec4f> plines ; //设置一个向量用于存放输出
HoughLinesP(image_in , plines , 1 , CV_PI/180 , 20 , 50 , 5);
for (size_t i = 0; i < plines.size(); i++) //划出直线
{
Vec4f point = plines[i]; //取出第i个直线的端点数据,四个数据依次为,x0,y0,x1,y1
line(image_out, Point(point[0], point[1]), Point(point[2], point[3]), Scalar(255, 255, 0), 2, LINE_AA);
}
要先进行Canny边缘检测再进行霍夫直线检测
圆
HoughCircles()函数
函数原型
void HoughCircles( InputArray image, OutputArray circles,int method, double dp, double minDist, double param1 = 100, double param2 = 100,int minRadius = 0, int maxRadius = 0 );
第一个参数是输入图像
第二个参数是输出数据
第三个参数是检测方法,只有CV_HOUGH_GRADIENT
第四个参数设为1
第五个参数是两个圆心之间的最小距离
第六个参数是Canny检测时的高阈值,低阈值默认为高阈值的一半
第七个参数是圆心的阈值,在极坐标系中至少多少个函数的交点会被认为是圆心
第八个参数是圆的最小半径
第九个参数是圆的最大半径
样例代码
vector<Vec3f> pcircles; //设置一个向量存储输出数据
HoughCircles(image_in , pcircles , CV_HOUGH_GRADIENT , 1 , 100 , 120 , 50 , 100 , 200);
for (size_t i = 0; i < pcircles.size(); i++) //画出所有的圆,输出的数据中三个数据依次为x0,y0,r
{
Vec3f circles = pcircles[i];
circle(dst,Point(circles[0],circles[1]),circles[2],Scalar(0,0,255),1,LINE_AA);
}
霍夫圆很容易受到椒盐噪声的影响,最好先进行中值滤波
直方图
直方图计算
calcHist()函数
函数原型
void calcHist( const Mat* images, int nimages,const int* channels, InputArray mask,SparseMat& hist, int dims,const int* histSize, const float** ranges,bool uniform = true, bool accumulate = false );
第一个参数是输入的图像数组,也可以是单一图像的指针
第二个参数是图像的个数
第三个参数是通道数的地址,需要计算的通道的下标,可以传入一个数组,如果是单通道就是只有0的数组。
第四个参数是掩膜,如果有的话就是只计算掩膜上非零位置,不使用的话可以输cv::Mat()
第五个参数是输出的直方图数据
第六个参数是直方图的维度,一般来说取1
第七个参数是每一维上直方图个数的指针,一般来说越大直方图越精细
第八个参数是要统计的灰度范围,用一维float数组装入最小值与最大值,再装入一个常量float指针数组。
第九个参数是是否归一化,默认开启
样例代码
int channels = 0; //设置通道数
float range[] = {0,256}; //将最小值与最大值存入一维float数组
const float* histrange[] = {range}; //将一维数组装入常量float指针数组
int hist_size = 200; //一共200个直方图
calcHist(image_in , 1 , &channels , Mat() , hist , 1 , &hist_size , histrange); //将直方图数据存入hist
normalize(hist , hist , 0 , image_out.rows , NORM_MINMIX);//进行归一化
double bin_w = (double)image_out.rows/hist_size;
for (int i = 1; i < hist_size; i++)
{
line(image_out , Point((i-1)*bin_w , image_out.rows - cvRound(hist.at<float>(i-1))) , Point(i*bin_w , image_out.rows - cvRound(hist.at<float>(i))) , Scalar(255,0,0) , 2 , LINE_AA);
} //cvRound是四舍五入取整
计算出直方图后要对结果数组进行进行归一化,然后循环画出直方图。
直方图对比
compareHist()函数
函数原型
double compareHist( InputArray H1, InputArray H2, int method );
第一个参数是输入直方图1
第二个参数是输入直方图2
第三个参数是对比模式
对比模式有:
相关性比较:CV_COMP_CORREL 概率论中的相关系数,绝对值为1时两图相同,为0时完全不相关
卡方比较: CV_COMP_CHISQR 返回值为0时两图相同,为1时两图无关
十字比较:CV_COMP_INTERSECT 取每个位置两者的最小值累加,不是很好用
巴氏距离:CV_COMP_BHATTACHARYYA 两图相同时为1,不相关时为0,相对而言效果最好
样例代码
double compare_12 = compareHist(hist_1 , hist_2 , CV_COMP_BHATTACHARYYA);
直方图对图像灰度比较敏感,要做比较时,最好先将图像转到HVS空间再进行直方图计算,最后进行比较。HSV图像计算直方图时,需要计算的通道为第一和第二通道。
直方图均衡化
equalizeHist()函数
函数原型
void equalizeHist( InputArray src, OutputArray dst );
第一个参数是输入图像,需要是单通道
第二个参数是输出图像
样例代码
equalizeHist(image_in , image_out);
直方图均衡化可以提高不同光照下的图像效果,使画面呈现出更多细节,增强后续处理的效果。
直方图反向映射
函数原型
calcBackProject( const Mat* images, int nimages,const int* channels, const SparseMat& hist, OutputArray backProject, const float** ranges,double scale = 1, bool uniform = true );
第一个参数是图像的地址
第二个参数是图像个数
第三个参数是通道的角标数组
第四个参数是直方图
第五个参数是输出图像
第六个参数是计算范围,与直方图计算里的那个一样
样例代码
int hist_size = 180; //直方图个数
float range[] = { 0 , 180 }; //统计范围,这里使用的是HSV图像,所以是0-180
const float* ranges[] = { range };
int channels[2] = { 0,1 }; //统计HSV图像的前两个通道
calcHist(&image_hsv, 1, channels, Mat(), image_hist, 1 , &hist_size, ranges); //计算出直方图
calcBackProject(&image_hsv, 1, channels, image_hist, hist_back, ranges); 直方图反向映射
直方图匹配
matchTemplate()函数
函数原型
void matchTemplate( InputArray image, InputArray templ,OutputArray result, int method, InputArray mask = noArray() );
第一个参数是待检测图像
第二个参数是用来检测的图像
第三个参数是输出图像
第四个参数是匹配模式
匹配模式有:
平方差匹配法:TM_SQDIFF归一化平方差匹配法:TM_SQDIFF_NORMED //这两个最佳匹配结果在0处
相关匹配法:TM_CCORR
归一化相关匹配法:TM_CCORR_NORMED //这两个最佳匹配结果在最大处
系数匹配法:TM_CCOEFF
归一化相关系数匹配法:TM_CCOEFF_NORMED //这两个最佳匹配结果在1,最差在-1,0为不相关
通常会使用归一化的匹配模式
样例代码
matchTemplate(image_src, temp, temp_out, TM_CCOEFF_NORMED); //在image_src上寻找与temp图像匹配的部分,并把计算结果输出到temp_out中
normalize(temp_out,temp_out, -1, 1, NORM_MINMAX); //对输出的匹配结果进行归一化
double MinVal = 0, MaxVal = 0; //用于存储匹配结果中的最大最小值
Point MinLoc(0, 0), MaxLoc(0, 0); //用于存储匹配结果中最大值与最小值的坐标
minMaxLoc(temp_out, &MinVal, &MaxVal, &MinLoc, &MaxLoc); //寻找最值坐标
circle(image_src, Point(MaxLoc.x + temp.cols / 2, MaxLoc.y + temp.rows / 2), 30, Scalar(0, 0, 255), 2, 8); //在原图上的最佳匹配点处画上圆,要注意如果是用平方差匹配要在最小值处画。
轮廓
轮廓发现
findContours()函数
函数原型
void findContours( InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode,int method, Point offset = Point());
第一个参数是输入的图像,需要是完成Canny边缘检查的图像
第二个参数是输出的轮廓,包含多个轮廓,每个轮廓又包含很多的点,所以一般用vector<vector(Point)>类型的数据进行储存
第三个参数是拓扑信息,里边存储了每个轮廓与其他轮廓的拓扑关系,后一个轮廓 前一个轮廓 父轮廓 内嵌轮廓,一共四个相关轮廓的索引编号,所以一般用vector<Vec4i>进行储存,拓扑信息与每个轮廓一一对应
第四个参数是轮廓检索模式
轮廓检索模式有:
只检测最外围轮廓: CV_RETR_EXTERNAL 内部轮廓被忽略
检测所有的轮廓: CV_RETR_LIST 检测所有轮廓但检测到的轮廓不建立等级关系,相互独立。所以不存在父轮廓和内嵌轮廓。拓扑信息的第三、四位默认为-1
检测所有的轮廓且建立两个等级关系:CV_RETR_CCOMP 不同轮廓间外围为顶层,如果内围轮廓内部也包括其他轮廓,则它也被视作顶层。
检测所有的轮廓且建立等级树: CV_RETR_TREE 会根据轮廓的关系建立等级树,外围包括内围,内围也可包括其他轮廓。
第五个参数是轮廓的呈现方法
呈现方法有:
获取每个轮廓的每个像素:CV_CHAIN_APPROX_NONE 将相互之间距离不超过1的点全部相连
只取轮廓的拐点像素:CV_CHAIN_APPROX_SIMPLE 对信息进行了压缩
teh-Chinl chain 近似算法:CV_CHAIN_APPROX_TC89_L1
CV_CHAIN_APPROX_TC89_KCOS
轮廓绘制
drawContours()函数
函数原型
void drawContours( InputOutputArray image, InputArrayOfArrays contours,int contourIdx, const Scalar& color, int thickness = 1, int lineType = LINE_8, InputArray hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point() );
第一个参数是绘制的图像
第二个参数是输入的轮廓信息
第三个参数是轮廓的索引号
第七个参数是拓扑信息
第八个参数是最大层数,0代表只绘制当前 ,1代表绘制当前以及内嵌
第九个参数是每个点的偏移量,一般不设置
样例代码
cvtColor(image_src, image_gray, CV_BGR2GRAY);
Canny(image_gray, image_canny, 40, 140);
Mat image_out(image_canny.size(),CV_8UC3);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(image_canny, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
for(size_t i = 0; i < contours.size();i++)
{
drawContours(image_out,contours,i,Scalar(255,0,0),1,LINE_AA,hierarchy);
}
imshow("image_out", image_out);
凸包
convexHull()函数
函数原型
void convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true );
第一个参数是输入的轮廓数据
第二个参数是输出的凸包数据,数据类型与轮廓数据一样 vector<vector<Point>>
第三个参数是检测的方向,默认为逆时针
第四个参数是操作标识符,一般不用设置
样例代码
vector<vector<Point>> contours ; //用于存储轮廓数据
vector<Vec4i> hierarchy; //用于存储轮廓的拓扑信息
findContours(image_canny, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS); //发现轮廓
vector<vector<Point>> convex(contours.size()); //用于存储凸包信息
for (size_t i = 0; i < contours.size(); i++)
{
convexHull(contours[i], convex[i]); //检测凸包
}
for(size_t i = 0; i < contours.size();i++)
{
drawContours(image_out,convex,i,Scalar(0,0,0),2 , LINE_AA ); //绘制凸包
}
imshow("image_out", image_out);
绘制矩形/椭圆框
压缩点集
approxPolyDP()函数
函数原型
void approxPolyDP( InputArray curve, OutputArray approxCurve,double epsilon, bool closed );
第一个参数是输入点集,一般是轮廓数据
第二个参数是压缩后的点集
第三个参数是压缩精度,代表压缩后曲线与原曲线的最大距离
第四个参数是是否闭合,true是闭合
获得矩形
无角度
boundingRect()函数
函数原型
Rect boundingRect( InputArray points );
参数为点集
返回值为Rect,所以一般用vector<Rect>进行储存
倾斜
minAreaRect()函数
函数原型
RotatedRect minAreaRect( InputArray points );
参数为点集
返回值为RotateRect,包含矩形的四个顶点。一般用vector<RotateRect>进行储存。
需要注意,这个函数对输入点集的个数有要求,至少要有四个点以上。
获得圆
圆形
minEnclosingCircle()函数
函数原型
void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius );
第一个参数是输入点集
第二个参数是圆心集合
第三个参数是半径集合
椭圆
fitEllipse()函数
函数原型
RotatedRect fitEllipse( InputArray points );
参数是输入点集
返回值与旋转矩形一致,需要注意这个函数的输入点集至少要有六个点,使用时需要进行判断。
样例代码
//拟合
vector<vector<Point>> approx(contours.size()); //存储压缩后的点集
vector<Rect> rect(contours.size()); //存储拟合的无角度矩形
vector<RotatedRect> rotate(contours.size()); //存储拟合的有角度矩形
vector<RotatedRect> elipse_(contours.size()); //存储拟合的椭圆
vector<Point2f> css(contours.size()); //存储拟合圆的圆心
vector<float> rad(contours.size()); //存储拟合圆的半径
for(int i=0 ; i<contours.size() ; i++) //遍历每一个轮廓点集
{
approxPolyDP(contours[i], approx[i], 3 , true); //压缩点集
rect[i] = boundingRect(approx[i]); //拟合无角度矩形
rotate[i] = minAreaRect(approx[i]); //拟合有角度矩形
minEnclosingCircle(approx[i], css[i], rad[i]); //拟合圆
if (approx[i].size() > 5) { //确保点集中至少六个点
elipse_[i] = fitEllipse(approx[i]); //拟合椭圆
}
}
//绘制
Point2f rotate_point[4]; //用于取用拟合有角度矩形的四个顶点数据
for (int i = 0; i < approx.size(); i++) //遍历每个轮廓进行绘制
{
rotate[i].points(rotate_point); //将顶点数据存入rotate_point
rectangle(image_src, rect[i], Scalar(0, 0, 0), 3, LINE_AA); //绘制无角度矩形
circle(image_src, css[i], rad[i], Scalar(100, 0, 100)); //绘制圆
ellipse(image_src, elipse_[i], Scalar(0, 100, 100)); //绘制椭圆
for (int k = 0; k < 4; k++)
{
line(image_src, rotate_point[k], rotate_point[(k+1)%4],Scalar(0,0,255)); //使用四个顶点数据绘制旋转矩形,(k+1)%4是为了使矩形闭合且不超出边界
}
}
图像矩以及轮廓面积/长度计算
图像矩
moments()函数与类
通过轮廓数据计算中心距,可以用来拟合轮廓质心
函数原型
Moments moments( InputArray array, bool binaryImage = false );
第一个参数是输入的轮廓数据
第二个参数是是否进行二值化,如果选true,灰度图中非零点均会被看作1
返回值的数据类型为Moments类,一般用vector<Moments>进行储存。
轮廓面积
contourArea()函数
通过轮廓数据计算轮廓面积
函数原型
double contourArea( InputArray contour, bool oriented = false );
第一个参数是输入的轮廓数据
轮廓长度
arcLength()函数
函数原型
double arcLength( InputArray curve, bool closed );
第一个参数是输入的轮廓数据
第二个参数是指定轮廓是否闭合
样例代码
vector<Moments> moment(contours.size()); //用于存储图像矩
vector<Point2f> points(contours.size()); //用于存储拟合的质心
for(size_t i = 0; i < contours.size();i++)
{
drawContours(image_out, contours, i, Scalar(0, 0, 0), 2, LINE_AA); //绘制轮廓
moment[i] = moments(contours[i]); //根据轮廓计算图像矩
contourArea(contours[i]); //根据轮廓计算面积
//拟合质心,质心的x为图像矩的m10/m00,y为图像矩的m01/m00,static_cast是为了保证数据类型为float
points[i] = Point(static_cast<float>(moment[i].m10 / moment[i].m00), static_cast<float>(moment[i].m01 / moment[i].m00));
circle(image_out, points[i], 2, Scalar(255, 255, 0), 2); //绘制质心
cout << "轮廓" << i << "的面积为" << contourArea(contours[i]) << endl << "长度为" << arcLength(contours[i],true)<<endl; //输出面积以及轮廓长度
}
imshow("image_out", image_out);
点多边形检测
用于检测点与封闭轮廓的位置关系
pointPolygonTest()函数
函数原型
double pointPolygonTest( InputArray contour, Point2f pt, bool measureDist );
第一个参数是输入轮廓数据
第二个参数是检测点
第三个参数是设定是否要返回距离,如果是true则返回值为该点到轮廓的距离(外部为正,内部为负),如果为false,则返回值1、0、-1分别代表轮廓外,轮廓上、轮廓内
分水岭算法
用于分离图像上的不同部分
这部分的代码 我一直编译失败,查了半天资料也没找出来问题,就先放下了,之后具体用到的时候再补上。