OpenCV (C++)学习笔记!!! 陆续更新

563 阅读44分钟

一. 基本数据类型

1.一些基本类型缩写:

bwsifd
unsigned charunsigned shortshortintfloatdouble

2. Point类

Point类属于模板类, 简单开销小. Point类通过别名调用, 并使用 . 访问成员变量, 例如:

cv::Point2icv::Point2fcv::Point2dcv::Point3icv::Point3fcv::Point3d

操作函数如下:

操作示例
默认构造函数cv::Point2i p;
cv::Point3i p;
复制构造函数cv::Point3f p2(p1);
值构造函数cv::Point2i p(x0,x2);
cv::Point3d p(x0, x1, x2);
构造成固定向量类(cv::Vec3f) p;
成员函数p.x, p.y, //三维中:P.Z
点乘float x = p1.dot(p2);
双精度点乘double x = p1.dot(p2)
叉乘p1.cross(p2) //三维中使用
判断一个点p是否在矩形r内p.inside(r) //三维中使用

可以转化到固定向量类.

3. Scalar类

一个四维点类. 所有成员都是双精度浮点数. 一些操作函数如下:

操作示例
默认构造函数cv::Scalar s;
复制构造函数cv::Scalar s2(s1);
值构造函数cv::Scalar s(x0);
cv::Scalar s(x0, x1, x2, x3);
元素相乘s1.mul(s2);
(四元数)共轭s.conj(); //return cv::Scalar(s0, -s1, -s2, 0s2);
(四元数)真值测试s.isReal(); //return true, if s1 == s2 == s3 == 0

4. size类

与Point类类似, 只要区别在于Point的数据成员是x和y(点), 而size类对应的数据成员是width和height.

size类拥有的三个别名分别为:

cv::Sizecv::Size2icv::Size2f

支持的操作为:

操作示例
默认构造函数cv::Size sz
cv::Size2i sz
cv::Size2f sz;
复制构造函数cv::SIze sz2(sz1)
值构造函数cv::Size2f sz(w, h)
成员访问sz.width;
sz.height
计算面积sz.area()

不支持转换到固定向量类.

5. cv::Rect类

矩形类包含Point类的成员x和y(矩形左上角)和size类的成员width和height(代表的矩形的大小和位置).

但矩阵并非继承自Point类或者size类, 所以不具有其相关操作.

支持的操作:

操作示例
默认构造函数cv::Rect r
复制构造函数cv::Rect r2(r1)
值构造函数cv::Rect(x, y, width, height)
由起始点和大小构造cv::Rect(p, sz)
由两个对角构造cv::Rect(p1, p2)
成员访问r.x; r.y; r.width; r.height
计算面积r.area()
提取左上角r.tl()
提取右下角r.br()
判断点p是否在矩形内r.contains(p)

覆写操作符:

操作示例
矩形r1和矩形r2的交集cv::Rect r3 = r1 & r2;
r1 &= r2;
同时包含矩形r1和矩形r2的最小面积矩形cv::Rect r3 = r1 | r2;
r1 |= r2;
矩形r平移x个数量cv::Rect rx = r + x;
r += x
矩形r扩大s大小cv::Rect rs = r + s;
r += s;
比较矩形r1与矩形r2是否相等bool eq = (r1 == r2)
比较矩形r1与矩形r2是否不想等bool ne = (r1 != r2)

6. cv::RotatedRect类

包含一个中心点cv::Point2f, 一个大小cv::Size2f和一个角度float的容器.

其中float表示图形围绕中心点旋转的角度.

cv::RotatedRect和cv::Rect有一个非常重要的不同点是cv::RotatedRect是以中心为原点, 而cv::Rect则以左上角为原点. 支持的操作:

操作示例
默认构造函数cv::RotatedRect rr()
复制构造函数cv::RotatedRect rr2(rr1)
从两个点构造cv::RotatedRect(p1, p2)
值构造函数, 需要一个中心点point,
一个大小size和一个角度float
cv::Rotated rr(point, size, float)
成员访问rr.center, rr.size, rr.float
返回四个角的列表rr.points(ptr[4])

7. 固定矩阵类

固定矩阵类需要在创建时知道矩阵的纬度(大小). 其分配和清楚都很快. 在小型矩阵(2x2, 3x3, 等等)矩阵上做过特别的优化.

一般来说, 在编译的时候知道矩阵的大小的时候使用固定矩阵类(比如相机矩阵), 而如果对象是一个图像或大型点列表这样的大数组的时候, 通常使用cv::Mat.

cv::Matx支持的操作:

操作示例
默认构造函数cv::Matx33f m33f;
cv::Matx43d m43d;
复制构造函数cv::Matx22d m22d(n22d)
值构造函数cv::Matx21f m(x0, x1);
cv::Matx44d m(x0, x1, ...., x14, x15);
含相同元素的矩阵m33f = cv::Matx33f::all( x )
全零矩阵m23d = cv::Matx23d::zeros();
全一矩阵m16f = cv::Matx16f::ones();
创建一个单位矩阵m33f = cv::Matx33f::eye();
成员访问m( i, j ), m( i )

8. 固定向量类

固定向量类从固定矩阵类派生出来, 可以说固定向量类是列为1的cv::Matx<>.

cv::Vec支持的操作:

操作示例
默认构造函数Vec2s v2s;
Vec6f v6f;
复制构造函数Vec3f u3f( v3f );
值构造函数Vec2f v2f(x0, x1);
Vec6d v6d(x0, x1, x2, x3, x4, x5);
成员访问v4f[ i ];
v3w( j ); //operator()和operator[]都可以
向量叉乘v3f.cross( u3f )

9. 复数类

二. 一些辅助对象

1. cv::TermCriteria类

用于算法的终止条件(有限迭代次数或某种形式的误差(epsilon)).

含有三个变量, 可以通过构造函数进行设置TermCriteria(int type, int maxCount, double epsilon);

type参数必须经过相应的设置以使用maxCount或epsilon. (可以设置为为TermCriteria::COUNT或者TermCriterai::EPS).

2. Range类

用于确定一个连续的整数序列, 拥有两个元素start和end, 可以在构造函数cv::Range(int start, int end)中进行设定, 范围包含初始值start, 但不包含终止值end

使用size()函数可以得到range类的元素数量.

三. 图像和大型数组类型

1. cv::Mat类N维稠密数组

可作为任意维度的数组使用, 数据按照栅格扫描顺序存储的n维数组.

所有的矩阵中都包含表示数组类型的元素flag, 表示维度的元素dims 分别表示行和列的数目元素row和cols 一个指示真正存储位置的data值镇 以及表示该内存区域有多少个引用的refcount元素.

初始化函数

构造函数

构造函数说明
cv::Mat默认构造函数
cv::Mat( int rows, int cols, int type );指定类型的二维数组
cv::Mat( int rows, int cols, int type,
const Scalar&s );
指定类型的二维数组, 并指定初始化值
cv::Mat( int rows, int cols, int type,
void *data, size_t step=AUTO_STEP );
指定类型的二维数组, 并指定预先存储的数据
cv::Mat( cv::Size sz, int type );指定类型的二维数组, (大小由sz指定)
cv::Mat( cv::Size sz, int type, const Scalar&s );指定类型的二维数组, 并指定初始化值
cv::Mat( cv::Size sz, int type, void *data,
size_t step=AUTO_STEP );
指定类型的二维数组,并指定预先存储的数据
cv::Mat( int ndims, const int* sizes, int type );指定类型的多维数组
cv::Mat( int ndims, const int* sizes, int type,
const Scalar&s );
指定类型的多维数组, 并指定初始化值
cv::Mat( int ndims, const int* sizes, int type,
void* data, size_t step=AUTO_STEP );
指定类型的多维数组, 并指定预先存储的数据

其可以分为三个类型: 输入行数和列数构造一个二维数组 使用cv::Size构造一个二维数组 构造n维数组并通过一个整型的序列来确定每一维数据

复制构造函数

复制构造函数描述
cv::Mat( const Mat& mat );复制构造函数
cv::Mat( const Mat& mat,
const cv::Range& rows,
const cv::Range& cols );
从指定的行列中复制数据
cv::Mat( const Mat& mat,
const cv::Rec& roi );
从感兴趣的区域中复制数据
cv::Mat( const Mat& mat,
cosnt cv::Range* ranges );
服务于n维数组的, 从泛化的感兴趣区域中复制数据
cv::Mat( const cv::MatExpr& expr );从其他矩阵的线性代数表述中生成新矩阵

其复制构造函数也分为三个类别: 输入行和列的范围 使用cv::Rect来指定一个矩形的子区域(只对二维矩阵有效) 输入一个range数组(range所指向的有效范围必须和mat的维度相等)

模板构造函数

模板构造函数描述
cv::Mat( const cv::Vec<T, n>& vec,
bool copyData = true );
构造一个如同cv::Vec所指定的
数据类型为T, 大小为n的一位
cv::Mat( const cv::Matx<T, m, n>& vec,
bool copyData = true );
构造一个如同cv:Matx所指定的
数据类型为T, 大小为m x nd的二维数组
cv::Mat( cosnt std::vector< T >& vec,
bool copyData = true );
构造STL的vector所指定的
数据类型为T, 大小为vector的元素的一维数组

静态方法构造函数

函数描述
cv::Mat::zeros ( rows, cols, type );构造一个rows x cols, 数据类型type,
值全为0的矩阵
cv::Mat::ones ( rows, cols, type );构造一个rows x cols, 数据类型type,
值全为1的矩阵
cv::Mat::eye ( rows, cols, type );构造一个rows x cols, 数据类型type,
的单位矩阵

数组数据获取

独立获取数组元素

通过模板函数**at<>()**来直接访问数组中的某一个元素, 其对不同维度的数组有不同的参数要求.

模板类要求先将**at<>()**转化为矩阵所包含的数据类型, 然后使用你所想要的数据的行和列的位置访问该元素.

例子:

//单通道访问单一元素
cv::Mat m = cv::Mat::eye(10, 10, CV_32FC1);		//单通道浮点型单位矩阵
cout << m.at<float>(3, 3) << endl;			//模板类使用float

//双通道访问单一元素
cv::Mat m2 = cv::Mat::eye(10, 10, CV_32FC2);	//双通道浮点型单位矩阵
cout << m.at<cv::Vec2f>(3, 3)[0] << endl;		//模板类使用Vec2f 访问0通道元素

注意: 当你想要对多维数组指定一个类似于at<>() 的模板函数时, 最好的方式是使用cv:Vec<>对象

at<>()访问函数的变体:

示例描述
M.at< int >( i );整型数组M中元素i
M.at< float >( i, j );浮点型数组M中的元素( i, j )
M.at< int >( pt );整型矩阵M中处于( pt.x, pt.y )的元素
M.at< float >( i, j, k );三维浮点型矩阵M中处于(i, j, k )位置的元素
M.at< uchar >( idx );无符号字符数组M中位于idx[]索引的n维位置的元素

也可以使用**ptr<>()**来访问数组, 得到指向数组的指针.

例如: 给定一个类型为float三通道的矩阵mtx, 结构体**mtx.ptr< Vec3f >(3)**将返回mtx的第三行指向第一个元素第一个(浮点)通道的指针.

经过本人实践,使用at会很大概率有问题。为了开发效率考虑,不要使用at;不要使用at;不要使用at;

请使用ptr;请使用prt;请使用ptr

注意访问图片的类型(char就是char,int就是int,不能混淆)

使用数组迭代器

cv::MatIterator<>

使用cv::Mat内嵌的迭代器机制, 这种机制在某种程度上是基于STL容器所提供的机制. 基础想法是OpenCV提供一对迭代器模板, 一个用于只读(Const)数组和一个用于非只读的数组. 上述迭代器分别被命名为cv::MatConstIterator<>和cv::MatIterator<>. cv::Mat的成员函数begin()和end()会返回这种类型的对象.

例子: 使用迭代器计算三通道三维数组中"最长"元素(一个三维向量域)

int sz[3] = {4, 4, 4};
cv::Mat m(3, sz, CV_32FC3);
//在m中填充-1.0 - 1.0的小数
cv::randu(m, -1.0f, 1.0f);

float max, len = 0.0f;
//使用迭代器模板类获取m的起始位置
cv::MatConstIterator_<cv::Vec3f> it = m.begin<cv::Vec3f>();

while(it != m.end<cv::Vec3f>())
{
    len = (*it)[0]*(*it)[0] + (*it)[1]*(*it)[1] + (*it)[2]*(*it)[2];
    if (len > max)
    max = len;
    it++;
}
NAryMatIlterator

该迭代器只要求被迭代的数组有相同的几何结构(维度以及每一个维度的范围).

与MatIterator不同, 该迭代器不会返回一个用于迭代的单独元素, 而通过返回一堆数组来进行N-ary迭代器操作, 这些返回的数组也称作"面"(Plane). 一个面表示输入数组有连续内存的部分.

通过块访问数组元素

该方法适用于: 需要将一个数组的子集作为另一个数组访问, 这个子集可能是一行或者一列, 也可能是原始数组的一个子集.

cv::Mat区块访问

示例描述
m.row( i );m中第 i 行数组
m.col( j );m中第 j 列数组
m.rowRange( i0, i1 );m中第 i0 行到第 i1行所构成的数组
m.rowRange( cv::Range( i0, i1 ));m中第 i0 行到第 i1 行所构成的数组
m.colRange( j0, j1 );m中第 j0 列到第 j1 列所构成的数据
m.colRange( cv::Range( i0, i1 ));m中第 j0 列到第 j1 列所构成的数据
m.diag( d );m中偏移为 d 的对角线所构成的数组
(若参数为0, 将会是主队角,
若为正数, 则相对于主队角向数组上半部分
偏移, 反之负数向下半部分偏移)
m( cv::Range( i0, i1), cv::Range( j0, j1 ));m中从点( i0, j0 )到点( i1, j1 )所包含数据构成的数组
m( cv;:Rect( i0, i1, w, h ));m中从点( i0, j0 )到点( i0+w-1, j0+h-1 )所包含的数据
构成的数组
m( ranges );m中依据ranges[0]到ranges[ndim-1]所索引区域
构成的数组

矩阵表达式

使用运算重载符运算矩阵cv::Mat

示例描述
m0 + m1, m0 - m1矩阵的加法和减法
m0 + s; m0 - s; s - m1;矩阵和单个元素的加和减
-m0;矩阵取负
s * m0; m0 * s;缩放矩阵;
m0.mul( m1 ); m0/m1;将元素m0和m1相乘, 按元素将m0和m1
相除;
m0 * m1;m0和m1进行矩阵乘法
m0.inv( method );对m0矩阵进行求逆(默认使用DECONP_LU);
m0.t();对m0矩阵求转置;
m0>m1; m0>=m1;
m0==m1; ....
按元素进行比较, 返回元素只有0和255的
uchar类型矩阵;
min(m0, m1); max(m0, m1); min(m0, s);
min(s, m0); max(m0, s); max(s, m0);
矩阵和矩阵之间或者矩阵和单个元素之间按元素
取最大或者最小值.
cv::abs( m0 );对m0按元素取绝对值;

其他一些操作:

示例描述
m0.cross( m1 ); m0.dot( m1 );向量叉乘和点乘操作;
(叉乘操作只适用于3x1矩阵);
cv::Mat::eye( Nr, Nc, type );
cv::Mat::zeros( Nr, Nc, type );
cv::Mat::ones( Nr, Nc, type );
用于返回规定类型NxN矩阵的静态方法;

矩阵求逆操作inv()实际上是一系列矩阵求逆操作的接口:

  • 使用cv::DECOMP_LU进行LU分解, 此方法对任意的非奇异矩阵都有效.
  • 使用cv::DECOMP_CHOLESKY, 表示使用Cholesky分解, 其在矩阵半正定的时候有效, 且在处理大型矩阵的时候比LU分解快很多.
  • 使用cv::DECOMP_SVD, 为奇异值分解(SVD)进行求逆, 在矩阵奇异或者非方阵的情况下都可以工作(可以求出矩阵的伪逆).

其他的一些成员函数

示例描述
m1 = m0.clone();从m0进行完全复制所有的数据元素
m0.copyTo( m1 );将m0复制给m1, 如果有必要, 将给m1重分配内存空间
(等同于m1 = m0.clone() );
m0.copyTo( m1, mask );只复制mask所指示的区域
m0.convertTo( m1, type, scale, offset );转换m0中元素的类型, 并且在尺度变换和增加偏置后赋值给m1;
m0.assignTo( m1, type );只在内部使用(集成在convertTo中)
m0.set( s, mask );将m0所有元素设为s, 如果存在mask, 则只对mask指示区域进行操作
m0.reshape( chan, rows );改变二维数组的有效形状, chan和rows若为0, 则便是不做更改
m0.push_back( s );在末尾增加一个mx1大小的数组
m0.push_back( m1 );向m x n大小的矩阵m0增加k行并复制到m1中, m1大小必须是k x n (扩充矩阵)
m0.pop_back( n );从m x n大小的矩阵移除n行
m0.locateROI( size, offset );将m0的全尺寸写入cv::Size size, 如果m0是一个大矩阵的一块区域, 还会
写入Point类型的offset
m0.adjustROI( t, b, l, r );通过t(最上), b(最下), l(最左), r(最右), 调整ROI范围
m0.total();计算数组序列的元素的数目(不包括通道)
m0.isContinous();如果m0的行之间没有空隙, 将返回true
m0.elemSize();返回m0的位长度(比如三通道浮点矩阵将返回12)
m0.elemSize1();返回m0的最基本元素的位长度(比如三通道浮点矩阵将返回4)
m0.type();返回m0的元素类型(比如: CV_32FC3)
m0.depth();返回m0通道中的元素类型(比如: CV_32F)
m0.channels();返回m0的通道数目
m0.size();以从cv::Size返回m0的大小
m0.empty();如果数组没有元素, 返回true

2. cv::SparseMat稀疏数据类

cv::SparseMat稀疏类矩阵在非0元素非常少的情况下使用(0很多), 在一些特殊的应用中, 大部分元素为空的时候使用稀疏类矩阵可以节约大量的内存. 但稀疏类矩阵计算时需要对每个元素进行计算, 其运算速度较慢.

Opencv的稀疏矩阵类cv::SparseMat函数在很多方面与稠密矩阵类cv::Mat的定义十分相似.

cv::Mat使用接近C风格(数据按照序列被打包, 且地址可以通过元素的索引计算出来), 而cv::SparseMat使用哈希表来存储非0元素, 这个哈希表会自动维护, 当数组中的非0数据变得太多以致于无法高效进行查找的时候, 表也会增长.

访问稀疏数组中的元素

函数访问

稀疏数组提供四种访问机制:

cv::SparseMat::ptr()cv::Sparse::ref()cv::SparseMat::value()cv::SparseMat::find()
1. ptr方法

cv::SparseMat::ptr()方法有几种变体, 最简单的如下:

uchar* cv::SparseMat::ptr(int i0, bool createMissing, size_t* hashval=0)

这个版本用于访问一维数组, 第一个参数i0是请求元素的索引, 第二个参数creatMissing表示这个元素是否应该被创建

2. ref方法

cv::SparseMat::ref<>()用于返回一个指向数组中特定元素的引用, 这个函数类似于SparseMat::ptr(), 可以接受一个、两个或者三个索引, 或者一个索引数组, 并且同样支持一个可选的用于查找的哈希值.

3. value方法

cv::SparseMat::value<>()和cv::SparseMat::ref<>()彼此独立, 它将返回一个值而不是返回值的引用, 因此, 这个方法也叫"只读方法"

4. find方法

返回一个请求对象的指针, 其指针类型由cv::SparseMat::find<>()的模板指定. 是一个"只读方法".

迭代器访问

迭代器访问可使用cv::SparseMatIterator_ <>() 和 cv::SparseMatConstIterator_ <>(), 其包含模板类和非模板类(可以不使用模板, 返回一个非模板的迭代器).

使用迭代器例子:

int size[] = {10, 10};
cv::SparseMat sm(2, size, CV_32F);

for (int i=0; i<10; i++)
{
    int idx[2];
    idx[0] = size[0] * rand();
    idx[1] = size[1] * rand();

    sm.ref<float>(idx) += 1.0f;     //对sm中的随即索引赋值
}
cv::SparseMatIterator_<float> it = sm.begin<float>();
cv::SparseMatIterator_<float> it_end = sm.end<float>();
for (; it != it_end; ++it)
{
    const cv::SparseMat::Node* node = it.node();
    cout << node->idx[0] << endl;
    cout << node->idx[1] << endl;
    cout << *it << endl << endl;
}

以上的例子中, 路过了定义在迭代器中的方法node(), node()返回一个指向被迭代器索引指向的稀疏矩阵的实际数据区域, 它返回一个类型为cv::SparseMat::Node的对象:

struct Node
{
	size_t hashval;
	size_t next;
	int idx[cv::MAX_DIM];
};

稀疏矩阵的特有函数

示例描述

3. 大型数组模板结构

四. 矩阵操作

矩阵函数

输入为矩阵类型, 输出为矩阵类型, 或者输入输出同为矩阵类型.

函数名称描述
cv::abs()计算矩阵中所有元素的绝对值
cv::absdiff()计算两个矩阵 差值的绝对值
cv::add()实现两个矩阵按元素相加
cv::addWeighted()1实现两个矩阵按元素加权求和
cv::bitwise_and()计算两个矩阵逐元素按位与
cv::bitwise_or()按位或
cv::bitwise_xor()按位异或
cv::bitwise_not按位非
cv::calcCovarMatrix()计算一组n维向量的协方差
cv::cartToPolar()计算二维向量的角度和幅度
cv::checkRange()检查矩阵的无效值
cv::compare()对两个矩阵中的所有元素应用所选择的比较运算符
cv::completeSymm()通过将一半元素复制到另一半来使矩阵对称
cv::convertScaleAbs()缩放矩阵, 取绝对值, 然后转换为8位无符号数
cv::countNonZero()
cv::arrToMat()
cv::dct()
cv::determinant()
cv::dft()
cv::divide()
cv::eigen()
cv::exp()
cv::extractImageCOI()
cv::flip()2将图像绕着x轴或y轴或同时绕x,y进行翻转,默认设置为0只绕着x轴翻转
cv::gemm()
cv::getConvertElem()
cv::getConvertScaleElem()
cv::idct()
cv::idft()
cv::inRange()
cv::invert()
cv::log()
cv::magnitude()
cv::LUT()
cv::Mahalanobis()
cv::max()
cv::mean()3计算输入矩阵src中未被屏蔽的所有像素的平均值
cv::meanStdDev()4计算输入矩阵src中未被屏蔽的像素的平均值以及它们的标准差, 如果src是多通道的, 则以每个通道为基础计算平均值和标准差
cv::merge()
cv::min()
cv::minMaxLoc()
cv::mixChannels()
cv::mulSpectrums()
cv::multiply()
cv::mulTransposed()
cv::norm()
cv::normalize()
cv::perspectiveTransform()
cv::phase()
cv::polarToCart()
cv::pow()
cv::randu()
cv::randn()
cv::randShuffle()
cv::reduce()
cv::repeat()
cv::saturate_cast<>()
cv::scaleAdd()
cv::setIdentity()
cv::solve()
cv::solveCubic()
cv::solvePoly()
cv::sort()
cv::sortIdx()
cv::split()5将一个多通道矩阵分割成多个单通道矩阵
cv::sqrt()
cv::subtract()
cv::sum()
cv::theRNG()
cv::trace()
cv::transform()
cv::transpose()

cv::addWeight()

void	cv::addWeight(
	cv::InputArray	src1,			//第一个输入矩阵
    double	alpha,						//第一个输入矩阵权重
    cv::InputArrary	src2,			//第二个输入矩阵
    double	beta,							//第二个输入矩阵权重
    double	gamma,					//offset added to weighted sum
    cv::OutputArray	dst,			//输入结果矩阵
    int		dtype = -1						//结果输出类型
);

此函数可以用于实现alpha混合. 参数α\alpha是src1的混合强度, β\beta是src2的混合强度.

cv::mean()

cv::Scalar	cv::mean(
	cv::InputArray	src,
    cv::InputArray	mask = cv::noArray()
);

计算src中未被mask屏蔽的像素均值

cv::meanStdDev()

void cv::meanStdDev(
	cv::InputArray	src,
    cv::OutputArray	mean,	//输出的均值
    cv::OutputArray	stdDev,	//输出的均方差
    cv::InputArray	mask = cv::noArray()
);

计算输入矩阵src中未被屏蔽的像素的平均值以及它们的标准差, 如果src是多通道的, 则以每个通道为基础计算平均值和标准差.

void cv::flip(
	cv::InputArray	src,
	cv::OutputArray	dst,
	int	flipCode = 0
);

该函数将图像绕着X轴或Y轴或者同时绕着X轴和Y轴翻转,默认情况下,flipCode将被设置为0,图像只绕着X轴翻转

如果flipCode被设置为大于0的数,图像会绕Y轴翻转,如果被设置为一个负值,图像将围绕X轴和Y轴翻转(0为绕着X轴翻转)

cv::split()

void	cv::split(
	const	cv::Mat&	mtx,
    cv::Mat*	mv
);
void	cv::split(
	const cv::Mat&	mtx,
    vector<Mat>&	mv				//STL类型
);

使用cv::split()将多通道矩阵中的通道分为多个单通道矩阵.

务必确保可用的cv::Mat对象的数量(至少)等于mtx中的通道数, 如果使用STL向量形式, cv::split()将为你分配结果矩阵的内存.

五. 绘图和注释

绘图

opencv的绘图函数可以在任意深度的图像上工作, 但在大多数情况下只对图像的前三个通道有影响.

想指定颜色的时候, 通常的做法是使用cv::Scalar对象, 即使大多数的时候只能用到最前面的三个值. 按照惯例, opencv在对多通道图像进行色彩渲染时使用了BGR的顺序.

绘图函数

函数描述
cv::circle()画一个圆
cv::clipLine()判断一条直线是否在给定的矩形内
cv::ellipse()画一个椭圆
cv::ellipse2Poly()计算一个近似椭圆的多边形
cv::fillConvexPoly()画一个填充的简单多边形
cv::line()画一个简单直线
cv::rectangle()画一个简单矩形
cv::polyLines()画多重折线

cv::circle()

void	circle(
	cv::Mat	&img,
    cv::Point	center,
    int	radius,
    const	cv::Scalar&	color,
    int	thickness = 1,
    int	lineType = 8,
    int	shift = 0
);
  • 第二个参数为圆心, 一个二维坐标; 第三个参数是半径
  • 颜色, 粗细, 线的类型

cv::clipLine()

cv::line()

void line(
	cv::Mat&	img,
    cv::Point	pt1,
    cv::Point	pt2,
    const	cv::Scalar&	color,
    int	lineType = 8,
    int	shift = 0
);

函数在图像img上绘制一条从pt1到pt2的直线, 直线自动被图像边缘截断

cv::rectangle()

void	rectangle(
	cv::Mat&	img,
    cv::Point	pt1,
    cv::Point	pt2,
    const	cv::Scalar&	color,
    int	lineType = 8,
    int	shift = 0
);
void	rectangle(
	cv::Mat&	img,
    cv::Rect	r,
    const	cv::Scalar&	color,
    int	lineType = 8,
    int	shift = 0
);

使用cv::rectangle()函数在img上绘制一个从角点pt1到pt2的矩形,或可以使用cv::Rect类型的参数来指定矩形

字体和文字

文字绘制函数

函数名称描述
cv::putText()在图像种绘制指定文字
cv::getTextSize()获取一个文字的宽度和高度

cv::putText()

cv::getTextSize()

六. OpenCV中的函数子

主成分分析cv::PCA

奇异值分解cv::SVD

随机数发生器cv::RNG

七. 图像、视频和数据文件

图像

从磁盘读取图像或者将图像保存到磁盘是我们完成的最基本的任务, 最简单的实现方法是利用OpenCV的cv::imread()cv::imwrite() 函数, 这些函数会帮助你完成包括与文件系统进行交互, 图像编码和解码在内的所有工作.

cv::imread()

用来读取图像, 其函数定义:

cv::Mat cv::imread(
    const string& filename, 					//inut name
    int flags = cv::IMREAD_COLOR		//flags set how to interpret file
);

flags可选参数:

标志含义默认值
cv::IMREAD_COLOR总是读取三通道图形图像
cv::IMREAD_GRAYSCALE总是读取单通道图像(灰度图像)
cv::IMREAD_ANYCOLOR通道数由文件实际通道数(不超过3)
cv::IMREAD_ANYDEPTH允许加载超过8bit深度
cv::IMREA_UNCHANGEDcv::IMREAD_ANYCOLOR和cv::IMREAD_ANYDEPTH组合

若cv::imread()载入图像失败, 并不会抛出一场, 而是返回一空的cv::Mat(可以用cv::Mat::empty()==ture来进行判断)

cv::imwrite()

用来保存图像, 其函数定义:

bool cv::imwrite(
	const string& filename, 		//Input filename
	cv::InputArry image, 				//Image to write to file
	const vector<int>& params = vector<int>()	//(Optional) for paramterized fmts
);

第一个参数定义了文件名, 文件拓展名部分可用来决定如何保存图像, OpenCV支持的常用拓展名:

  • .jpg或者.jpeg 8位数据;单通道或三通道输入
  • .jp2
  • .tif或.tiff 8位或者16位数据; 单通道, 三通道或四通道输入
  • .png 8/16位数据; 单, 三, 四通道输入
  • .bmp 8位数据; 单, 三, 四通道输入
  • .ppm 8位数据; 单, 三通道

第三个参数被用作特殊类型文件的写入操作时所需的数据. 输入参数为内部为整型数据的一个STL vector.

其具体内容为: 一系列的参数ID, 以及与该参数对应的参数值, 每个参数ID之后跟着其对应的值

参数ID起的别名以及取值范围:

标志含义取值范围默认值
cv::IMWRITE_JPG_QUALITYJPEG的质量0~10095
cv::IMWRITE_PNG_COMPRESSIONPNG的压缩值(值高压缩率高)0~93
cv::IMWEITE_PXM_BINARY对PPM, PNG或PBM文件是否使用二进制0或11

若成功保存图像则返回true, 反之, 返回false

cv::imencode()

用于对图像进行编码

cv::imdecode()

用于对图像进行解码

视频

cv::VideoCapture

构造方式

cv::VideoCapture::VideoCapture(
	const string& filename			//input filename
);
cv::VideoCapture::VideoCapture(
	int device										//Viedo capture device id
);
cv::VideoCapture::VideoCapture();

第一个构造函数中, 需要给出视频的文件名, 若打开成功会返回true, 可以通过使用cv::VideoCapture::isOpen()来对返回值进行检查.

第二个构造函数中, 若当前系统中只有一个摄像机时参数为0, 当有多个相机时, 参数随之递增.

第三个构造函数可以通过先声明对象而不提供关于即将打开的硬件设备的任何信息.

cv::VideoCapture cap;
cap.open("my_video.avi")

在这种情况下, cv::VideoCapture::open()函数最终产生的效果与调用相同参数的cv::VideoCapture构造函数相同.

read()

用于从视频流中读取图像

bool cv::VideoCapture::read(
	cv::OutputArray image					//image into which to read data
);

该函数的作用是从cv::VideoCapture表示的文件中读取下一帧数据, 并将数据插入你提供的变量中, 同时, 这个行为将使VideoCapture对象更新, 以方便读取下一帧. 若读取没有成功(比如已经读取到文件的最后一帧), 那么该函数将返回false.

opertor>>()

从视频流中读取图像数据.

cv::VideoCapture&	cv::VideoCapture::operator>>(
	cv::Mat&	image							//image into which to read data
);

作为对cv::VideoCapture::read()函数的补充, 你还可以使用重载函数cv::VideoCapture::operator>>() (输入流操作符号)从VideoCapture对象中读取下一帧数据.

grab()和etrieve()

*一个捕获操作(类似于内存的拷贝) 和 恢复操作(对已抓取的数据进行解码). *

bool cv::VideoCapture::grab(void);
bool cv::VideoCapture::retrieve(
	cv::OutputArray	image, 
	int channel = 0 									//used for mulihead devices
);

grab()函数将当前的图像数据拷贝到用户看不见的内存缓冲区. grab()函数只是设计用于将原始图像数据尽快获取到电脑.

tips

之所以将捕获(grab) 和 恢复(retrieve)分开进行而不是像read()那样将捕获和解码同时进行是因为, 多相机的捕获是最常见的一种情况(例如立体成像). 在这种情况下需要缩短多个相机获取的图像帧之间的时间差. 因为对于数据的抓取, 将数据安全放入缓存区之后在进行解码恢复, 这样做非常有意义.

get()和set()

用于获取相机属性

double cv::VideoCapture::get(
	int		propid									//Property identifier
);
bool cv::VideoCapture::set(
	int propid,										//Property identifier
	double	value								//Value to which to set the property
);

cv::VideoCapture::get()常用属性:

视频捕获属性是否只有在摄像头
模式下使用
含义
cv::CAP_PROP_POS_MSEC视频文件中的当前位置(毫秒)或视频捕获
时间戳
cv::CAP_PROP_POS_FRAMES从零开始下一帧的索引
cv::CAP_PROP_POS_AVI_RATIO视频中的相对位置(范围从0.0到1.0)
cv::CAP_PROP_FRAME_WIDTH视频帧的像素宽度
cv::CAP_PROP_FRAME_HEIGHT视频帧的像素高度
cv::CAP_PROP_FPS录制视频的帧速率
cv::CAP_PROP_FOURCC四个字符代码指示编解码
cv::CAP_PROP_FRAME_COUNT视频文件的帧总数
cv::CAP_PROP_FORMAT返回的Mat对象的格式(例如CV_8UC3)
cv::CAP_RPOP_MODE表示捕捉模式, 值是特定与正在使用的后端
cv::CAP_PROP_BRIGHTNESS:heavy_check_mark:相机的亮度设置(支持时)
cv::CAP_PROP_CONTRAST:heavy_check_mark:相机的对比度设置(支持时)
cv::CAP_PROP_SATURATION:heavy_check_mark:相机饱和度设置(支持时)
cv::CAP_PROP_HUE:heavy_check_mark:相机色调设置(支持时)
cv::CAP_PROP_GAIN:heavy_check_mark:相机的增益设置(支持时)
cv::CAP_PROP_EXPOSURE:heavy_check_mark:相机曝光设置(支持时)
cv::CAP_PROP_CONVERT_RGB:heavy_check_mark:如果非零, 捕获的图像被转化为具有三个通道
cv::CAP_PROP_WHITE_BALANCe:heavy_check_mark:相机的白平衡设置(支持时)
cv::CAP_PROP_RECTIFICATION:heavy_check_mark:立体相机整流标志

这里的所有参数都以double类型返回

例子: 获取视频宽高:

cv::VideoCapture cap("../../resource/video/test_for_car.mp4");
double videoWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);
double videoHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);

cv::VideoWriter

用于将视频写入磁盘

构造方法

//默认构造函数
cv::VideoWriter::VideoWriter
//参数构造函数
cv::VideoWriter::VideoWriter(
	const	string&	filename, 				//input filename
	int		fourcc,											//编码
	double	fps,										//帧率
	cv::SIze	frame_size,
	bool	is_color = ture
);

也可以使用open方法对其初始化的对象进行设置:

cv::ViedoWriter	out;
out.open(
	"my_video.mpg",
    CV_FOURCC('D', 'I', 'V', 'X'),
    30.0,
    cv::Size(640, 480)
);

可是使用isOpened函数对VideoWriter对象进行查询其是否已经完全就绪, 若就绪, 返回true, 否则返回false

wirte()

确定号VideoWriter对象后可以同多wirte对其进行写入:

cv::VideoWrite::write(
	const Mat&	image			//要写入的数据帧
);

operator<<()

也可以像cout一样使用<<()向视频写入图像数据:

myVideoWriter	<<	myFrame;

数据存储

cv::FileStorage

用于将不同数据类型的和数据以YAMLXML格式写入磁盘或者从磁盘读取, 这些方法可以用来加载或保存任何OpenCV的数值变量(包括基本数据变量)到一个文件中.

构造方法

FileSorage::FileStorage();
FileStorage;:FileStorage(
	string fileName, 
	int flag
);

写入

默认构造函数创建对象后, 可以通过open函数进行打开XMLYAML格式的文件.

flag参数可以为cv::FileStorage::WRITE或cv::FileStorage::APPEND

FileStorage::open(	string fileName,	int flag );

打开文件后可以通过**cv::FileStorage::operator<<()**进行写入操作

在cv::FileStorage内部数据的存储主要有两种形式, "mapping" (健/值对)和 "sequence" (一系列为命名的条目)

在最顶层, 所写入的数据都在一个mapping中, 在该mapping中, 你可以放置其他的mapping或者sequences, 甚至在mapping中继续放入mapping等.

myFileStorage	<<	"someInteger"	<<	27;									//存储一个整数
myFileStorage	<<	"anArray"	<<	cv::Mat::eye( 3, 3, CV_32F );		//存储一个矩阵

可以使用 { 创建mapping, 使用 [ 创建sequences.

创建maping:

myFileStorage	<<	"theCat"	<<	"{";
myFileStorage	<<	"fur"	<<	"gray"	<<	"eyes"	<<	"green"	<<	"weightLbs"	<<	16;
myFileStorage	<<	"}";

创建一个mapping后, 需要按照顺序输入条目名以及对应的值. 如果创建的是sequence, 只需要一个接一个输入元素即可, 知道sequence结束.

myFileStorage	<<	"theTeam"	<<	"[";
myFileStorage	<<	"eddie"	<<	"tom"	<<	"scott";
myFileStorage	<<	"]";

一旦完成工作, 可以使用**cv::FileStorage::release()**关闭该文件.

读取

cv::FileStorage对象在打开之后既可以用于写入, 也可以用作读取. 唯一的区别是参数flag的值为cv::FileStorage::READ. 和写入时一样, 也可以用默认构造函数和创建一个FileStorage对象, 之后再通过**cv::FileStorage::open*()**函数打开.

八. 平台

鼠标

OpenCV通过回调函数使用鼠标,以下为定义回调函数必须匹配的协议:

void	your_mouse_callback(
	int	event,							//事件类型
	int	x,									//鼠标事件的X位置
	int y,									//y
	int flags,							//鼠标事件的更多细节
	void *param					//Parameters from cv::setMouseCallback()
);

第一个参数表:

事件数值
cv::EVENT_MOUSEMOVE0
cv::EVENT_LBUTTONDOWN1
cv::EVENT_RBUTTONDOWN2
cv::EVENT_MBUTTONDOWN3
cv::EVENT_LBUTTONUP4
cv::EVENT_RBUTTONUP5
cv::EVENT_MBUTTONUP6
cv::EVENT_LBUTTONDBLCLK7
cv::EVENT_RBUTTONDBLCLK8
cv::EVENT_MBUTTONDBCLK9

第四个标志为鼠标事件标志, 用于鼠标组合键(如: shift+鼠标左键):

标志数值
cv::EVENT_FLAG_LBUTTON1
cv::EVENT_FLAG_RBUTTON2
cv::EVENT_FLAG_MBUTTON4
cv::EVENT_FLAG_CTRLKEY8
cv::EVENT_FLAG_SHIFTKEY16
cv::EVENT_FLAG_ALTKEY32

最后一个参数是void类型的指针, OpcnCV可以用来传递额外的信息到任何类型的结构.

需要一个函数来注册这个回调函数, 即cv::setMouseCallback(), 它需要三个参数:

void	cv::setMouseCallback(
	const	string&	windowName,					//应用回调函数的窗口
	cv::MouseCallback	on_mouse,			//要注册的回调函数
	void*	param = NULL								//回调函数的额外参数, 可以给回调函数传递信息
);

滑动条,滚动条和开关

HighGUI中创建一个滑动条的函数如下:

int	cv::creatTrackbar(
	const 	string&		trackbarname,			//滑动条名称
    cosnt	string&		windowName,			//窗口名称
    int*	value,													//滑动的条当前位置
    int		cout,														//滑动条总的数量
    cv::TrackbarCallback	onChange = NULL,	//回调函数(可选)	(当滑动按钮移动时, 回调函数自动调用)
    void*	param												//额外参数
);

这个回调函数并非必须的. 若无回调函数, 移动滑动钮的唯一响应就是value指向的变量值的变化(可以通过这个值来对值的变化做出响应)

回调函数所满足的形式:

void yourTrackbarCallback(
	int		pos,						//滑动条位置
    void*	param = NULL		//cv::setTrackbarCallback()的参数
);

c::creatTrackbar()的最后一个参数是params, 可以是一个任意类型的指针, 一旦回调执行, 这个参数可以传递给回调函数的params参数, 这样一来, 不创建全局变量也可以处理滑动条事件.

有两个函数可以用来获取和设置滑动条的位置值:

int	cv::getTrackbarPos(
	const	string&	trackbarName,			//滑动条名称
    const	string&	windowName				//窗口名称
);
void	cv::setTrackbarPos(
	const	string&	trackbarName,
    const 	string&	windowName,
    int pos															//设置的位置值
);

通过Qt后端工作

文字蒙版

用于在窗口上方显示文字条幅, 其函数API:

int	cv::displayOverlay(
	const	string&	name,					//显示的窗口名称
    const	string&	text,						//显示的文字
    int		delay										//显示的ms时间(0 = 'forever')
);

最后一个参数中, 若delay的值设置为0, 那么蒙版将一直存在或直到你再次调用cv::displayOverlay()覆盖原来的蒙版, 若在delay结束前调用cv::dispalyOverlay(), 则将显示新的蒙版, 且延时重置.

创建按钮

Qt接口提供创建按钮的函数, 包括正常的按钮, 单选按钮和复选框, 所有创建的按钮都位于控制面板上.

所有三种类型的按钮都通过一个方法完成:

int	cv::creatButton(
	const	string&	buttonName,									//按钮名称
    cv::ButtonCallback	onChange = NULL,			//按钮事件的回调函数(可无)
    void*	params,																//额外的按钮事件参数
    int	buttonType = cv::PUSH_BUTTON,				//按钮类型(PUSH_BUTTON or RADIOBOX or CHECKBOX)
    int	initalState = 0															//按钮的初始化状态
);

按钮回调函数定义:

void	yourButtonCallback(
	int		state,								//按钮状态
    void*	params						//cv::creatButton传递的参数
);

你传递给cv::creatButton()的params指针也会被传给回调函数的params参数.

buttonType可以取值为: cv::PUSH_BUTTON, cv::RADIOBOX或者cv::CHECKBOX:

第一个为标准按钮, 当你点击按钮便会调用回调函数

第二个为复选框, 按钮参数根据有没有选择复选框设置为1或者0

第三个为单选按钮, 区别在于点击按钮同时调用当前点击按钮的回调函数和当前没有被点击的按钮(互斥状态).

文本和文字

使用Qt接口写文本必须首先创建一个CvFont对象, 任何时候想在屏幕中添加文本都可以使用这个对象, 字体通过函数cv::fontQt()创建:

CvFont	fontQt(
	const	string&	fontName,								//字体名称
    int	pointSize,														//字体大小
    cv::Scalar	color = cv::Scalar::all(0),				//BGR颜色
    int	weight = cv::FONT_NORMAL,					//字体宽度(粗细), 1-100(Table	9-3)
    int	spacing = 0															//字体间距
);

Qt字体粗细的预定以名称及与其关联的值:

Camere capture constant数值
cv::FONT_LIGHT25
cv::FONT_NORMAL50
cv::FONT_DEMIBOLD63
cv::FONT_BOLD75
cv::FONT_BLACK87

最后一个参数spacing控制字符间距, 值正负都可以

放置文本函数:

void	cv::addText(
	cv::Mat&	image,				//要写入的图片
    const	string&	text,			//要写入的文本
    cv::Point	location,			//左下角定位的文本位置
    CvFont*	font						//OpenCV字体的结构体
);

文本的位置为首字符左下角的位置(文本基线的起点)

设置和获取窗口属性

大部分基于Qt后端创建的窗口状态属性都是可以获取的, 并且大部分都能够在创建窗口后更改:

void	cv::setWindowProperty(
	const	string&	name,						//窗口名称
    int	prop_id,										 //窗口属性ID
    double	prop_value							//设置的属性值
);
double	cv::getWindowProperty(
	const	string&	name,					//窗口名称
    int	prop_id											//窗口的属性ID
);

获取属性通过提供窗口名和属性ID(prop_id)就可以, 这是属性需要额外提供属性表:

属性名称描述
cv::WIND_PROP_FULL_SIZE设置为cv::WINDOW_FULLSCREEN可以将窗口显示为全屏
设置为cv::WINDOW_NORMAL窗口显示为正常尺寸
cv::WIND_PROP_AUTOSIZE设置为cv::WINDOW_AUTOSIZE窗口大小自动调整为图像大小
设置为cv::WINDOW_NORMAL显示图像大小调整为窗口大小
cv::WIND_PROP_ASPECTRATIO设置为cv::WINDOW_FREERATIO可以使窗口有任意长宽比(用户可调整)
设置为cv::WINDOW_KEEPRATIO只能按照固定长宽比调整窗口大小

保存和恢复窗口的状态

Qt接口可以保存和恢复窗口的状态:

void	cv::savaWindowParameters(
	const	string&	name											//窗口名
);
void	cv::loadWindowParameters(
	const	string&	name
);

保存窗口时使用cv::saveWindowParameters即可, 恢复窗口通过cv::loadWIndowParameters()即可.

即使你退出或者重启程序, 属性加载命令依然能够正常工作.

和OpenGL交互

回调函数需要满足的定义:

void	yourOpenGLCallback(
	void*	params											//从cv::createOpenGLCallback()传过来的参数
);

可以通过cv::createOpenGLCallback()配置回调函数:

void	cv::createOpenGLCallback(
	const	string&	windowName,					//窗口名称
    cv::OpenGLCallback	callback,				//OpenGL的回调函数
    void*	params = NULL								//传给回调函数的参数
);

参数可以在调用回调函数时给函数以额外的参数.

九. 滤波与卷积

边界外推和边界处理

自定义边框

在对图像进行卷积、核操作或想要为图像添加边框时可以对图像增加边框。

cv::copyMakeBorder()

void	cv::copyMakeBorder(
	cv::InputArray	src, //输入图像
    cv::OutputArray	dst,	//输出图像
    int 	top,
    int		bottom,
    int		left,
    int 	right,
    int 	borderType,		//像素外推方法
    const	cv::Scalar& value = cv::Scalar()
);

borderType:

  • cv::BORDER_CONSTANT 为每个边框像素赋予一个相同的值*
  • cv::BORDER_WRAP 为每个像素分配一个距离,距离是像素点到源图像边缘的距离,然后将距离对边相同的像素的值赋予对应的边框像素*
  • cv::BORDER_REPLICATE 将源图像以外的像素全部赋值为图像边缘像素的值
  • cv::BORDER_REFLECT 为边框内每个像素复制了源图像内距离同一边界n+1对应的像素点的值(镜像复制拓展边界)
  • cv::BORDER_REFLECT_101与cv::BORDER_REFLECT效果类似,通常是OpenCV的默认选项(镜像复制拓展边界,边界像素除外)

自定义外推

阈值化操作

cv::threshold函数

double	cv::threshold(
	cv::InputArray	src,
    cv::OutputArray	dst,
    double	thresh,						//阈值值
    double	maxValue,				//最大值
    int		thresholdType			//阈值处理类型
);

每种阈值化操作类型对应于第i个源像素(srci) 和阈值thresh之间的比较运算方式, 根据源像素和阈值之间的关系, 目标像素dsti可以被赋值为0, srci或给定的最大值maxValue.

cv::threshold()中thresholdType的可选项:

阈值类型操作
cv::THRESH_BINARYDSTi = (SRCi > thresh) ? MAXVALUE : 0
cv::THRESH_BINARY_INVDSTi = (SRCi > thresh) ? 0 : MAXVALUE
cv::THRESH_TRUNCDSTi = (SRCi > thresh) ? THRESH : SRCi
cv::THRESH_T0ZERODSTi = (SRCi > thresh) ? SRCi : 0
cv::THRESH_T0ZERO_INVDSTi = (SRCi > thresh) ? 0 : SRCi

Otus算法

函数cv::threshold()也可以自动决定最优的阈值, 只需要对参数thresh传递值cv::THRESH_OTUS即可.

Otus算法遍历所有可能的阈值, 然后对每个阈值结果的两类像素计算方差σi2{\sigma_i}^2(即低于阈值和高于阈值的两类像素).Otus算法计算方差使下列表达式最小:

σi2=w1(t)σ12+w2(t)σ22{\sigma_i}^2 = w_1(t) \cdot {\sigma_1}^2 + w_2(t) \cdot {\sigma_2}^2

式中的w1(t)w_1(t)w2(t)w_2(t)是根据两类像素的数量计算而来的权值, σ12{\sigma_1}^2σ22{\sigma_2}^2表示两类像素的方差. 实际上, 这种方法不是一个相对高效的过程.

自适应阈值

阈值可以在整个过程中自动产生变化:

void	cv::adaptiveThreshold(
	cv::InputArray	src,					//输入图像
    cv::OutputArray	ds,					//输出图像
    double	maxValue,					//最大值
    int		adaptiveMethod,				//方法或者高斯分布
    int		thresholdType,				//阈值类型
    int		blockSize,							//块的大小
    double	C										//常数
);

根据adaptiveMethod的设置, 可以使用两种不同的自适应阈值方法. 两种方法都是逐个像素地计算自适应阈值T(x, y), 方法是通过计算每个像素位置周围的b x b区域的加权平均值然后减去常数C, 其中b由blockSize给定.

当图像中出现较大的明暗差异时, 自适应阈值非常有效. 这个函数仅醋栗单通道8位或浮点型图像, 并且要求源图像和目标图像不同.

平滑

平滑也成为"模糊", 平滑图像的目的有很多, 但通常都是为了减少噪声和伪影. 在降低分辨率的时候, 平滑也十分重要.

简单模糊和方框型滤波器

void	cv::blur(
	cv::InputArray	src,
    cv::OutputArray	dst,
    cv::Size	ksize,									//核大小
    cv::Point	anchor = cv::Point(-1, -1),		//Location of anchor point
    int		borderType = cv::BORDER_DEFAULT		//border extrapolation to use(边界插值)
);

图像中的每个值都是源图像中相应位置一个窗口(核)中像素的平均值(窗口尺寸通过ksize声明),

anchor指定计算时核与源图像的对齐方式, 默认情况下anchor位cv::Point(-1, -1), 表示核相对于滤波器剧中.

简单模糊是方框型滤波器(Box Filter)的一种特殊形式. 方框型滤波器中所有值ki,jk_{i,j}全部相等(为1或者1/A, 其中A为滤波器的面积).

void	cv::boxFilter(
	cv::InputArray	src,
    cv::OutputArray	dst,
    int		ddepth,										//输出深度(e.g. CV_8U)
    cv::Size	ksize,
    cv::Point	anchor = cv::Point(-1, -1),
    bool	normalize = true,				//ture: divide by box area
    int		borderType = cv::BORDER_DEFAULt
);

变量ddepth的值设为-1则目标图像的深度与源图像保持一致, 否则可以设置为其他任何一种常用别名.

中值滤波器

将每个像素围绕这个像素的矩形邻域内的中值或者"中值"像素(相对于平均像素).

少量具有较大偏差的点也会严重影响到均值滤波, 中值滤波可以采用取中间点的方式来消除异常值.

void	cv::medianBlut(
	cv::InputArray	src,
    cv::OutputArray	dst,
    cv::Size	ksize
);

高斯滤波器

void	cv::GaussianBlur(
	cv::InputArray	src,
    cv::OutputArray	dst,
    cv::Size	ksize,
    double	sigmaX,								//Gaussian half-width in x-direction
    double	sigmaY = 0.0,
    int	borderType = cv::BORDER_DEFAULT		//border extrapllation to use
);

参数sigmaX表示高斯核在x方向上的sigma值(最大值的半宽); 第四个参数表示y方向上的sigam值.

若两者都设为0, 则高斯参数根据以下公式确定:

σx=(nx12)0.30+0.80,nx=ksize.width1\sigma_x = (\frac{n_x-1}{2})\cdot0.30 + 0.80, n_x = ksize.width-1
σy=(ny12)0.30+0.80,ny=ksize.height=1\sigma_y = (\frac{n_y-1}{2})\cdot0.30+0.80, n_y=ksize.height=1

双边滤波器

void	cv::bilateraFilter(
	cv::InputArray	src,
    cv::OutputArray	dst,
    int	d,														//像素领域大小(最大距离)
    double	sigmaColor,						//Width param for color weight function
    double	sigmaSpace,						//width param for spatial weight function
    int	borderType = cv::BORDER_DEFAULT
);

第三个参数是像素邻域的直径d, 第四个参数是颜色空间滤波器的sigma值sigmaColor, 第五个参数是坐标空间中滤波器的sigma值sigmaSpace. 第三个参数越大, 平滑时所包括的强度(色彩)越大(因此图像的不连续性将会更显著).

双边滤波器是一种比较大的图像分析算子, 也就是边缘保持平滑.

tips:

  1. 滤波器的大小d对算法的效率有很大影响, 通常在视频处理时不大于5, 但在非实时应用是这个值可以放大到9, 你也可以在调用这个函数时将其设为-1, 函数将自动为图像计算sigmaSpcae变量的值.
  2. 实际情况中, 小的sigmaSpace值比如10会带来一个轻微的但也明显的效果; 而大的sigmaSpace值比如150会对图像产生非常显著的影响, 使图像有一种卡通的效果.
  3. 高斯模糊的过程是减缓像素在空间上的变化, 因此与邻域的关系紧密, 高斯平滑很好地减弱了噪声并且保留了小信号, 但破坏了边缘信息, 最终是高斯模糊把边缘也模糊了.
  4. 可以把双边滤波当作是高斯平滑, 只是相似程度更高的像素权值更高, 边缘更明显, 对比度更高. 双边滤波的效果就是将源图像变成一幅水彩画, 这种效果在多次迭代后效果显著, 因此这种方法在图像分割领域十分有用.

导数和梯度

索贝尔导数

void	cv::Sobel(
	cv::InputArray	src,
    cv::OutputArray	dst,
    int		ddepth,									//输出的图像深度(e.g. CV_8U)
    int		xorder,									 //x中相应导数的阶
    int		yorder,									//y中相应导数的阶
    cv::Size	ksize = 3
);

xorder和yorder是求导顺序, 其取值范围为0, 1和2. 0表示在这个方向上不进行求导(xorder和yorder不能同时取0).

Scharr滤波器

拉普拉斯变换

图像形态学

膨胀和腐蚀

最基础的形态学变换是膨胀和腐蚀, 常应用于: 消除噪声, 元素分割和连接等. 基于这两种操作, 可以实现更复杂的形态学操作, 用来定位强度峰值或孔洞, 另一种形式的图像梯度等.

膨胀是一种卷积操作, 它将目标像素的值替换为卷积核覆盖区域的局部最大值. 膨胀的作用是使填充区域生长.

与膨胀相反, 腐蚀操作计算的是核覆盖范围内的局部最小值.

tips:

图像的形态学操作通常在阈值化操作后的布尔图像上进行.

总的来说, 膨胀扩张了明亮区域, 腐蚀缩减了明亮区域. 另外, 膨胀填充凹面, 腐蚀消除突起.

//腐蚀
void	cv::erode(
	cv::InputArray	src,
    cv::OutputArray	dst,
    cv::InputArray	element,											//Structuring, a cv::Mat()
    cv::Point	anchor = cv::Point(-1, -1),
    int	iterations = 1,															//迭代次数
    int	borderType = cv::BORDER_CONSTANT,
    const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue()
);
//膨胀
void	cv::dilate(
	cv::InputArray	src,
    cv::OutputArray	dst,
    cv::InputArray	element,
    cv::Point	anchor = cv::Point(-1, -1),
    int	iterations = 1,
    int	borderType = cv::BORDER_CONSTAN,
    const	cv::Scalar&	borderValue = cv::morphologyDefaultBorderValue()
);

腐蚀和膨胀支持原地调用(源图像和目标图像是同一副图像).

第三个元素是核, 可以向它传递一个未被初始化的cv::Mat().

膨胀dilate

腐蚀erode

通用形态学操作

void	cv::morphologEx(
	cv::InputArray	src,
    cv::OutputArray	dst,
    int	op,
    cv::InputArray	element,
    cv::Point	anchor = cv::Point(-1, -1),
    int	iterations = 1,
    int	borderType = cv::BORDER_DEFUALT,
    const	cv::Scalar&	borderValue = cv::morphologyDefaultBorderValue()
);

参数op:

操作值形态学操作是否需要临时图像
cv::MOP_OPEN开操作
cv::MOP_CLOSE闭操作
cv::MOP_GRADIENT形态学梯度总是需要
cv::MOP_TOPHAT顶帽操作就地调用需要(src = dst)
cv::MOP_BLACKHAT底帽操作就地调用需要(src = dst)

开操作和闭操作

  1. 开操作先将图像腐蚀,然后对腐蚀的结果膨胀(常用于对二值图像中的区域进行计数)(放大裂缝和局部小洞)
  2. 闭操作先将图像进行膨胀, 然后对膨胀的结果进行腐蚀(常用于减少无用或噪声驱动的片段)

形态学梯度

顶帽和黑帽

分别用于显示与其邻域相比更亮或更暗的部分.

TopHat(src)=srcopen(src)TopHat(src) = src - open(src)
BlackHat(src)=close(src)srcBlackHat(src) = close(src) - src

顶帽操作用源图像减去对其开操作后的图像

黑帽操作用闭操作后的图像减去源图像

自定义核

自定义形态学核的函数:

cv::Mat	cv::getStructuringElement(
	int	shapce,													//Element shape
    cv::Size	ksize,											//size fo structuring element(odd num!)
    cv::Point	anchor = cv::Point(-1, -1)	//默认锚点在元素中心
);

第一个参数控制构造元素的基本形状, 第二第三个参数确定元素的大小和锚点位置.

cv::getStructuringElement()的元素形状:

形状值元素描述
cv::MORPH_RECT矩形Ei,j=1,i,jE_{i,j}=1,\forall{i,j}
cv::MORPH_ELLIPSE椭圆形以ksize, width和ksize,height为
两个半径做椭圆
cv::MORPH_CROSS交叉Ei,j=1E_{i,j}=1i==anchor.yi==anchor.yj==anchor.xj==anchor.x

用线性滤波器做卷积

用cv::filter2D()做卷积

通过cv::sepFilter2D使用可分核

生成卷积核

十. 常见的图像变换

拉伸,收缩,扭曲和旋转

均匀调整

void	cv::resize(
	cv::InputArray	src,
    cv::OutputArray	dst,
    cv::Size	dsize,
    double	fx = 0,
    double	fy = 0,
    int	interpolation = CV::INTER-LINEAR	//(插值) 插值方法
);

有两种方式指定输出图像的大小:

  1. 使用绝对尺寸, dsize参数直接设置为我们想要的结果图像dst的大小
  2. 使用相对尺寸, dsize设置为cv::Size(0,0), 并分别将fx和fy设置为我们要应用于x轴和y轴的比例因子

插值参数:

插值含义
cv::INTER_NEAREST最近邻插值
cv::INTER_LINEAR双线性插值
cv::INTER_AREA像素区域重采样
cv::INTER_CUBIC双三次插值
cv::INTER_LANCZ0S4插值(超过8x8个邻域)

图像金字塔

cv::pyrDown()

void	cv::pyrDown(
	cv:InputArray	src,
    cv::OutputArray	dst,
    const	cv::Size& dstsize = cv::Size()			//输出图像大小
);

若第三个参数dstsize设置为默认值cv::Size(), 则输出图像为为原图的1/4, 具体为:

( (src.cols+1)/2), (src.rows+1)/2 )

cv::buildPyramid()

构建一系列的新图像, 每个都从其前身缩减.

void	cv::buildPyramid(
	cv::InputArray							src,
    cv::OutputArrayOfArrays		dst,
    int 												maxlevel						//金字塔等级
);

参数dst为cv::OutputArrayOfArray, 可以视为只是一个STL向量<>或cv::OutputArray类型的对象. 最常见的例子是< cv::Mat >向量.

cv::pyrUp()

放大图像:

void	cv::pyrUp(
	cv::InputArray		src,
    cv::OutputArray	dst,
    const cv::Size&	dstsize = cv::Size()
);

类似于cv::pyrDown(), 如果dstsize设置为默认值cv::Size(), 结果图像将与src的大小(每个维度)的两倍大小相同.

拉普拉斯金字塔

不均匀映射

拉伸, 收缩, 扭曲, 旋转 --> 几何变换

仿射变换

十一.图像分析

Canny边缘检测

cv::Canny()

在Canny算法中, 先在x和y方向上求得一阶导, 然后将它们组合成四个方向的导数. 其中方向导数是局部最大值的点是组成边缘的候选项. Canny算法最明显的创新点就是将单个的边缘候选像素加入轮廓.

void cv::Canny(
	cv::InputArray	image,
    cv::OutputArray	edges,
    double	threshold1,
    double	threshold2,
    int	apertureSize = 3,
    bool	L2gradient = false
);
  • 第一个参数是输入图像, 必须是单通道图像, 输出图像是灰度图(实际上是布尔图)
  • 接下来的两个参数是低阈值和高阈值
  • apertureSize是cv::Canny()内部调用的Sobel导数算子所用的aperture的大小
  • 最后的一个参数L2gradient用于选择是使用适当的L2范数"正确"计算方向梯度

Hough变换

Hough变换是一种用于检测线, 圆或者图像中其他简单形状的方法.

Hough线变换

OpenCV支持三种Hough线变换: 标准Hough变换(SHT), 多尺度Hough变换(MHT)和渐进概率Hough变换(PPHT).

cv::HoughLines():标准和多尺度Hough变换

都由函数cv::HoughLines()实现, 两种不同的用法可由两个可选的参数区分.

void cv::HoughLines(
	cv::InputArray	image,
    cv::OutputArray	lines,
    double	rho,
    double theta,
    int	threshold,
    double	srn = 0,
    double	stn = 0
);
  • 第一个参数是输入图像, 必须是8位图像, 被当做二值图
  • 第二个参数是存放结果位置, 是一个Nx1的双通道浮点型数组(N为返回的直线数目)
  • 两个通道包含每条线的rho(ρ\rho)和theta(θ\theta)值, 用于设置直线所需的分辨率, rho的单位是像素, 而theta的单位是弧度, 累加平面可以考虑成一个由θ\theta弧度大小为ρ\rho像素的单元组成的二维直方图. (例如theta可设置为CV_PI/180)
  • threshold是累加平面中算法用于判断线条属于一条直线的阈值.
  • 参数srn和stn用于控制称为"多尺度Hough变换"(MHT)的SHT算法的拓展, 将这些参数设置为0时, 函数将采用SHT算法

cv::HoughLinesP(): 渐进概率Hough变换

void cv::HoughLinesP(
	cv::InputArray	image,
    cv::OutputArray	lines,
    double	rho,
    double	theta,
    int	threshold,
    double	minLineLength = 0,
    double	maxLineGap = 0
);

与HoughLines的区别在于:

  • lines参数变为四通道(或者一个Vec4i类型的向量). 四通道分别是找出线段两个端点的坐标(x0,y0)(x_0,y_0)(x1,y1)(x_1,y_1).
  • 第二个重要的区别是两个参数的含义, PPHT中, minLineLength和maxLineLengGap参数设置了返回线段的最小长度, 以及共线线段之间的最小间隔, 防止算法把他们连接成一条.

Hough圆变换

void	cv::HoughCircles(
	cv::InputArray	image,
    cv::OutputArray	circles,
    int	method,
    double	dp,
    double	minDist,
    double	param1 = 100,
    double	param2 = 100,
    int	minRadius = 0,
    int	maxRadius = 0
);
  • 输入图像是一幅8位图像, 不要求是二值图(其内部使用的cv::Sobel())
  • 数组Circles是一个矩阵数组或向量, 取决于传入cv::HoughCircles()的内容, 如果是个矩阵, 它将是CV::F32C3类型的一维数组, 三个通道分别是圆的坐标及半径. 如果是个向量, 那么它的类型是vector< Vec3f >
  • method参数的值实际设置为cv::HOUGHGRADIENT
  • 参数dp是所使用的累加图像的分辨率, 如果dp设置为1, 那么分辨率不发生变化, 如果设置为一个较大的数比如2, 累加器的分辨率会减少一半, dp的值不能小于1
  • 参数minDist是两个圆之间必须存在的最小距离
  • param1和param2分别是边阈值和累加器阈值, Canny边检测器需要两个阈值
  • 最后连个参数是可以找到的圆的最小和最大半径, 这意味着它们是累加器所能表示的圆的半径

分割

漫水填充

漫水填充通常用于标记或隔离图像中的部分以做进一步的处理和分析,也可以从输入图像中导出可以由后续例程使用的掩膜。函数cv::floodFill()提供了一个可供选参数mask用于控制填充区域,例如对同一个图像的多个填充。

算法会线选取一个种子点,然后将所有与它相似的点的包括本身染上一种特定的颜色,区别是相邻像素不一定全部上同一种颜色,漫水填充的结果是一个单连通区域,如果像素灰度值与任一当前像素差异在指定范围(loDiff-upDiff)内火灾指定原始种子像素灰度值的指定范围内,cv::floodFill()函数都将对它进行上色. 漫水填充也可以通过可选的掩膜参数进行约束.

int cv::floodFill(
	cv::InputOutputArray	image,
    cv::Point	seed,
    cv::Scalar	newVal,		//value for painted pixels
    cv::Rect*	rect,			//output bounds painted domain
    cv::Scalar	lowDiff = cv::Scalar(),		//maximum down color distance
    cv::Scalar	highDiff = cv::Scalar(),	//maximum up color distance
    int	flags							//local/global, and mask-only
);
int cv::floodFill(
	cv::InputOutputArray	image,
    cv::InputOutputArray	mask,
    cv::Point	seed,
    cv::Scalar	newVal,
    cv::Rect*	rect,
    cv::Scalar	lowDiff = cv::Scalar(),
    cv::Scalar	highDiff = cv::Scalar(),
    int	flags
);
  • image是输入图像,可以是8位或浮点型图像,通道必须是1或3. 该函数会修改image
    • mask参数表示一个可以同时用作cv::floodFill()的输入掩膜(这种情况下会限制可以被填充的区域),并且可以作为cv::floodFill()的输出。mask必须是单通道8位图像,大小比原图像宽高分别大2像素
    • mask作为cv::floodFill()的输入时,算法不会填充掩膜中非零区域,因此,如果不想屏蔽掩膜操作,应该在使用前将其置零。
    • mask存在时,也将用作输出,当算法运行后,掩膜中每个“填充”像素将被设置为掩膜中的非零值。你可以向flags添加cv::FLOODFILL_MASK_ONLY(通过布尔OR)执行此选项, 这样, 输入图像就不会被修改, 相反, 只有掩膜会被修改.
  • 漫水填充从种子seed开始
  • 所有符合符合条件的值会被设为newVal颜色
  • 如果一个像素的强度不小于一个着色相邻像素的强度减去lowDiff,并且不大于着色像素的强度加上upDiff,则该像素将被着色。lowDiff(seedv)upDifflowDiff \le (seed - v) \le upDiff
    • 如果flags参数包含了cv::FLOODFILL_FIXE_RANGE,那么比较时将会选用种子点而不是相邻像素。通常flags参数控制填充的连接性。填充相对于何处填充,是否只填充一个掩膜区域,以及填充掩膜的值。
    • flags还有cv::FLOODFILL_MASK_NOLY, 出了这些, 你还可以为它加数值4或8. 这样, 你就声明了连通方式是4连通还是8连通. 前一种情况下, 4连通数组指的是

分水岭算法

Mean-Shift分割算法

void cv::pyrMeanShiftFiltering(
	cv::InputArray	src,
    cv::OuputArray	dst,
    cv::double	sp,
    cv::double	sr,
    int	maxLevel = 1,
    cv::TermCriteria	termcrit = TermCriteria(
    	cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS,
        5,
        1
    )
);

十四. 轮廓

轮廓查找

轮廓具有层次结构

cv::findContours()

void cv::findContours(
	cv::InputOutputArray	image,		
    cv::OutputArrayOfArrays	contours, //vector<vector<Point>>
    cv::OutputArray		hierarchy,
    int mode,
    int	method,
    cv::Point	offset = cv::Point()
);
void cv::findContours(
	cv::InputOutputArray	image,
    cv::OutputArrayOfArrays	contours,
    int mode,
    int method,
    cv::Point	offset = cv::Point()
);

第一个参数是输入图像, 图像必须是8位单通道图像, 并且应该被转化为二值的. 该图像在运行时会被直接涂改. 第二个参数是Vector数组. 第三个参数hierarchy, 如果给定参数则hierarchy将输出所有轮廓的树结构, 输出仍然为数组, 数组中的每个值都是四元数组.

参数mode使用OpenCV期望的轮廓提取方式, 有4中取值:

  1. cv::RETR_EXTERNAL

    只检索最外层轮廓

  2. cv::RETR_LIST

    检索所有轮廓并保存到表(List)中, 通过hierarchy[i][0进行连接

  3. cv::RETR_CCOMP

    检索所有的轮廓, 并将它们组织成双层结构, 顶层边界是所有成分的外部边界, 地二层边界是孔的边界.

  4. cv::RETR_TREE

    检索所有轮廓并重新建立网状轮廓结构

以下的值和method有关:

  1. cv::CHAIN_APPROX_NONE

    将轮廓编码中的所有点转换为点

  2. cv::CHAIN_APPROX_SIMPLE

    压缩水平, 垂直, 斜的部分, 只保留最后一个点, 在许多特殊情况下, 这一操作将大大减少返回的点数

  3. cv::CHAIN_APPROX_TC89_L1 or cv::CHAIN_APPROX_TC90_KCOS

    使用Teh-Chin链逼近算法中一个

最后一个参数是offset, 返回的轮廓中所有点都会根据参数值发生偏移.

绘制轮廓

void	cv::drawContours(
	cv::InputOutputArray	image,
    cv::InputArrayOfArrays	contours,
    int contourIdx,
    const	cv::Scalar&	color,
    int thickness = 1,
    int lineType = 8,
    cv::InputArray	hierarchy = noArray(),
    int	maxLevel = INT_MAX,
    cv::Point	offset = cv::Point()
);

第一个参数为待绘制轮廓的图像, 第二个参数contours是要绘制的轮廓, 与findContours的输出contours相同. 第三个参数contourIdx用于告诉cv::drawContours()需要绘制的是contoures参数中的某一条轮廓还是全部轮廓. 假如contourIdx是一个正数, 则对应的轮廓被绘制, 假如contourIdx为负数(-1), 则所有的轮廓将被绘制.

参数color为四元组cv::Scalar, 参数thickness是一个整数, 表示绘制的线的粗细, 参数lineType可以是4和8, 代表绘制的线将是四联通(不美观), 八连通(较美观)或是cv::AA线(美观).

参数hierarchy对应cv::findContours()函数输出的层次, 其与参数maxLevel共同起作用, maxLevel限制将在图上绘制的轮廓层次深度, 将maxLevel设为0表示只绘制"第0层",(最高层次) 的轮廓. 设为非0正数, 则表示绘制最高层一下相同数量层级的轮廓. 这对轮廓树很有用, 同样, 当你希望在连接成分(cv::RETR_CCOMP)时希望只显示最外层轮廓(而不显示内部孔轮廓)时, 这也很用帮助.

offset是绘制时的偏移.

轮廓几何

多边形逼近

几何及其特性

获取轮廓长度

double	cv::arcLength(
	cv::InputArray	points,
    bool	closed		//轮廓是否闭合
);

获取矩形包围框

cv::Rect	cv::boundingRect(
	cv::InputArray	points
);

获得最小矩形框

cv::RotatedRect	cv::minAreaRect(
	cv::InputArray	points
);

拟合

直线拟合

void	cv::fitLine(
	cv::InputArray	points,
    cv::OutputArray	line,
    int	distType,
    double	param,
    double	reps,
    double	aeps
);
  • 参数Points表示了一组cv::Mat数组或标准模板库向量形式的点集.

  • cv::fitLine()可以处理二维点, 也可以处理三维点, 不同之处体现在line上, cv::Vec4f(二维直线), cv::Vec6f(三维直线).

  • 第三个参数distType允许我们选择所用的距离度量.

  • param用于为其中一些距离度量提供参数值, 设置为0cv::fitLine()函数将自动为选中的距离度量提供最优值.

  • 参数reps和aeps代表你对拟合直线的原点精度要求和角度精度要求, 常用的值为1e-2

十六. 关键点和描述子

关键点和跟踪基础

角点检测

cv::goodFeaturesToTrack()

cv::goodFeaturesToTrack()使用了哈尔的方法,并在性能上略有改善,该函数便于计算必要的导数运算符,并对其进行分析,返回符合我们定义的适合跟踪的点的列表:

void	cv::goodFeaturesToTrack(
	cv::InputArray	image,
    cv::OutputArray	corners,
    int	maxCorners,
    double	qualityLevel,
    double minDistance,
    cv::InputArray	mask = noArray()
    int	blockSize = 3,
    bool	useHarrisDetector = false,
    double k = 0.04
);
  • 输入图像可以是任意8位或32位(即8U和32F)的单通道图像.
  • 输出角点将是包含所有到角点的向量或数组(取决你提供的内容), 如果它是vector<>形式的, 它应该是cv::Point2f对象的vector, 如果它是一个cv::Mat, 它的一行将是角点, 两列分别是点的x和y的位置.
  • 你可以使用maxCorners限制找到的角点数量, 用quality Level来设置点的(通常介于0.10和0.01之间, 绝对不超过1.0)的返回质量以及用minDistance设置相邻角之间的最小间距.
  • 如果要设置mask参数, 必须与图像的尺寸相同, 角点将不会在mask为0的地方生成
  • blockSize参数表示计算角点时需要考虑的区域大小, 默认值为3, 单对于高分辨率图像, 你可能想让它再稍微大一点
  • useHarrisDistance参数(如果设置为true)将设定cv::goodFeaturesTotrack()使用哈尔原始算法的精确角点强度公式, 如果设置为false, 将使用Shi和Tomasi的方法.
  • 参数k只能用于哈尔算法, 并且最好保留其默认值.

这是一个测试

test

test

Footnotes

  1. cv::addWeight

  2. cv::flip

  3. cv::mean()

  4. cv::meanStdDev

  5. cv::split()