提交 eedbd58a 编写于 作者: W wizardforcel

2021-02-18 22:03:08

上级 db1b1fac
......@@ -64,7 +64,7 @@
![How it works...](img/3241_06_17.jpg)
选择归一化系数`A`,以使不同的权重之和为 1。 `σ`值控制所得高斯函数的宽度。 该值越大,功能越平坦。 例如,如果我们计算间隔为`[-4,...,0,...4]``σ=0.5`的一维高斯过滤器的系数,则可以获得:
选择归一化系数`A`,以使不同的权重之和为 1。 `σ`值控制所得高斯函数的宽度。 该值越大,函数越平坦。 例如,如果我们计算间隔为`[-4,...,0,...4]``σ=0.5`的一维高斯过滤器的系数,则可以获得:
```cpp
[0.0 0.0 0.00026 0.10645 0.78657 0.10645 0.00026 0.0 0.0]
......@@ -98,7 +98,7 @@ cv::Mat reducedImage; // to contain reduced image
cv::pyrDown(image,reducedImage); // reduce image size by half
```
该相机使用`5x5`高斯过滤器对图像进行低通。 还存在使图像尺寸加倍的倒数`cv::pyrUp`函数。 当然,如果先缩小图像再放大,您将无法恢复确切的原始图像。 在缩编过程中丢失的内容无法恢复。 这两个功能用于创建**图像金字塔**。 这是一种由不同大小的图像的堆叠版本构成的数据结构(通常每个级别是前一级别的大小的一半),通常是为了进行有效的图像分析而构建的。 例如,如果希望检测图像中的物体,则可以首先在金字塔顶部的小图像上完成检测,并且在找到感兴趣的物体时,可以通过移到更低的金字塔等级来细化搜索,它包含图像高分辨率版本。
该相机使用`5x5`高斯过滤器对图像进行低通。 还存在使图像尺寸加倍的倒数`cv::pyrUp`函数。 当然,如果先缩小图像再放大,您将无法恢复确切的原始图像。 在缩编过程中丢失的内容无法恢复。 这两个函数用于创建**图像金字塔**。 这是一种由不同大小的图像的堆叠版本构成的数据结构(通常每个级别是前一级别的大小的一半),通常是为了进行有效的图像分析而构建的。 例如,如果希望检测图像中的物体,则可以首先在金字塔顶部的小图像上完成检测,并且在找到感兴趣的物体时,可以通过移到更低的金字塔等级来细化搜索,它包含图像高分辨率版本。
请注意,还有一个更通用的`cv:resize`函数,可让您指定所需的结果图像尺寸。 您只需指定一个可以小于或大于原始图像的新尺寸来调用它:
......@@ -114,7 +114,7 @@ cv::resize(image,resizedImage,
函数`cv::boxFilter`过滤具有仅由 1s 构成的正方形核的图像。 它类似于均值过滤器,但不将结果除以系数数量。
在第 2 章“使用访问邻居扫描图像”的“更多”部分中介绍了`cv::filter2D`函数。 此功能可让您通过输入所选的核将线性过滤器应用于图像。
在第 2 章“使用访问邻居扫描图像”的“更多”部分中介绍了`cv::filter2D`函数。 此函数可让您通过输入所选的核将线性过滤器应用于图像。
# 使用中值过滤器过滤图像
......@@ -154,7 +154,7 @@ cv::resize(image,resizedImage,
## 操作步骤
我们将在这里使用的过滤器称为 Sobel 过滤器。 之所以称为定向过滤器,是因为它仅影响垂直或水平图像频率,具体取决于所使用的过滤器核。 OpenCV 具有将 Sobel 运算符应用于图像的功能。 水平过滤器的名称如下:
我们将在这里使用的过滤器称为 Sobel 过滤器。 之所以称为定向过滤器,是因为它仅影响垂直或水平图像频率,具体取决于所使用的过滤器核。 OpenCV 具有将 Sobel 运算符应用于图像的函数。 水平过滤器的名称如下:
```cpp
cv::Sobel(image,sobelX,CV_8U,1,0,3,0.4,128);
......@@ -436,7 +436,7 @@ class LaplacianZC {
至于 Sobel 运算符,也可以使用更大的核来计算拉普拉斯算子,并且由于该运算符对图像噪声更加敏感,因此希望这样做(除非考虑到计算效率)。 请注意,拉普拉斯算子的核值总和为 0。这保证了在恒定强度的区域中拉普拉斯算子将为零。 确实,由于拉普拉斯算子测量图像函数的曲率,因此在平坦区域上它应等于 0。
乍一看,拉普拉斯算子的作用可能难以解释。 根据核的定义,很明显,运算符会放大任何孤立的像素值(该值与相邻像素值非常不同)。 这是操作人员对噪声的高度敏感性的结果。 但是,查看图像边缘周围的拉普拉斯值更有趣。 图像中存在边缘是不同灰度强度的区域之间快速过渡的结果。 随着图像功能沿边缘的演变(例如,由从暗到亮的过渡引起),人们可以观察到灰度级提升必然意味着从正曲率逐渐过渡(当强度值开始上升时) )变为负曲率(强度即将达到其高平稳期时)。 因此,正和负拉普拉斯值之间(或相反)之间的过渡构成边缘存在的良好指示。 表达这一事实的另一种方式是说边缘将位于拉普拉斯函数的*零交叉点* 。 我们将通过在测试图像的一个小窗口中查看拉普拉斯算子的值来说明这一想法。 我们选择一个对应于由城堡之一的塔的屋顶的底部创建的边缘的边缘。 下图绘制了一个白框,以显示该兴趣区域的确切位置:
乍一看,拉普拉斯算子的作用可能难以解释。 根据核的定义,很明显,运算符会放大任何孤立的像素值(该值与相邻像素值非常不同)。 这是操作人员对噪声的高度敏感性的结果。 但是,查看图像边缘周围的拉普拉斯值更有趣。 图像中存在边缘是不同灰度强度的区域之间快速过渡的结果。 随着图像函数沿边缘的演变(例如,由从暗到亮的过渡引起),人们可以观察到灰度级提升必然意味着从正曲率逐渐过渡(当强度值开始上升时) )变为负曲率(强度即将达到其高平稳期时)。 因此,正和负拉普拉斯值之间(或相反)之间的过渡构成边缘存在的良好指示。 表达这一事实的另一种方式是说边缘将位于拉普拉斯函数的*零交叉点* 。 我们将通过在测试图像的一个小窗口中查看拉普拉斯算子的值来说明这一想法。 我们选择一个对应于由城堡之一的塔的屋顶的底部创建的边缘的边缘。 下图绘制了一个白框,以显示该兴趣区域的确切位置:
![How it works...](img/3241OS_06_13.jpg)
......
......@@ -10,7 +10,7 @@
# 简介
为了对图像执行基于内容的分析,必须从构成图像的像素集合中提取有意义的特征。 轮廓,线条,斑点等是定义图像内容的基本图像元素。 本章将教您如何提取其中一些重要的图像功能
为了对图像执行基于内容的分析,必须从构成图像的像素集合中提取有意义的特征。 轮廓,线条,斑点等是定义图像内容的基本图像元素。 本章将教您如何提取其中一些重要的图像特征
# 使用 Canny 运算符检测图像轮廓
......@@ -71,7 +71,7 @@ The classic article by J. Canny, A computational approach to edge detection, IEE
# 使用霍夫变换检测图像中的直线
在我们的人造世界中,平面和线性结构比比皆是。 结果,在图像中经常可见直线。 这些有意义的功能在对象识别和图像理解中起着重要作用。 因此,检测图像中的这些特定特征很有用。 **霍夫变换**是实现此目标的经典算法。 它最初是为检测图像中的线条而开发的,并且正如我们将看到的,它也可以扩展为检测其他简单图像结构。
在我们的人造世界中,平面和线性结构比比皆是。 结果,在图像中经常可见直线。 这些有意义的特征在对象识别和图像理解中起着重要作用。 因此,检测图像中的这些特定特征很有用。 **霍夫变换**是实现此目标的经典算法。 它最初是为检测图像中的线条而开发的,并且正如我们将看到的,它也可以扩展为检测其他简单图像结构。
## 准备
......@@ -100,7 +100,7 @@ OpenCV 为行检测提供了霍夫变换的两种实现。 基本版本是`cv::H
80); // minimum number of votes
```
参数 3 和 4 对应于行搜索的步长。 在我们的示例中,该函数将按`1`搜索所有可能半径的线,并按`π/180`搜索所有可能角度的线。 下一部分将说明最后一个参数的作用。 通过这种特殊的参数值选择,可以在先前秘籍的道路图像上检测到 15 条线。 为了可视化检测结果,有趣的是在原始图像上绘制这些线。 但是,重要的是要注意该算法检测图像中的线,而不是线段,因为未给出每条线的终点。 因此,我们将绘制横贯整个图像的线。 为此,对于一条几乎垂直的线,我们计算其与图像的水平界限的交点(即第一行和最后一行),并在这两点之间绘制一条线。 我们几乎以水平线进行类似处理,但使用第一列和最后一列。 使用`cv::line`函数绘制线。 请注意,即使点坐标超出图像限制,此功能也可以正常使用。 因此,不需要检查计算出的交点是否落在图像内。 然后通过如下迭代线向量来绘制线:
参数 3 和 4 对应于行搜索的步长。 在我们的示例中,该函数将按`1`搜索所有可能半径的线,并按`π/180`搜索所有可能角度的线。 下一部分将说明最后一个参数的作用。 通过这种特殊的参数值选择,可以在先前秘籍的道路图像上检测到 15 条线。 为了可视化检测结果,有趣的是在原始图像上绘制这些线。 但是,重要的是要注意该算法检测图像中的线,而不是线段,因为未给出每条线的终点。 因此,我们将绘制横贯整个图像的线。 为此,对于一条几乎垂直的线,我们计算其与图像的水平界限的交点(即第一行和最后一行),并在这两点之间绘制一条线。 我们几乎以水平线进行类似处理,但使用第一列和最后一列。 使用`cv::line`函数绘制线。 请注意,即使点坐标超出图像限制,此函数也可以正常使用。 因此,不需要检查计算出的交点是否落在图像内。 然后通过如下迭代线向量来绘制线:
```cpp
std::vector<cv::Vec2f>::const_iterator it= lines.begin();
......@@ -442,7 +442,7 @@ The article by H.K. Yuen, J. Princen, J. Illingworth, and J Kittler, Comparative
0.01,0.01); // accuracy
```
这为我们提供了线方程的参数,其形式为单位方向向量(`cv::Vec4f`的前两个值)和线上一个点的坐标(`cv::Vec4f`的后两个值)。 对于我们的示例,这些值对于方向向量是`(0.83, 0.55)`,对于点坐标是`(366.1, 289.1)`。 最后两个参数指定线参数的要求精度。 注意,`std::vector`中包含的输入点根据功能需要在`cv::Mat`中传输。
这为我们提供了线方程的参数,其形式为单位方向向量(`cv::Vec4f`的前两个值)和线上一个点的坐标(`cv::Vec4f`的后两个值)。 对于我们的示例,这些值对于方向向量是`(0.83, 0.55)`,对于点坐标是`(366.1, 289.1)`。 最后两个参数指定线参数的要求精度。 注意,`std::vector`中包含的输入点根据函数需要在`cv::Mat`中传输。
通常,线方程将用于某些属性的计算中(在需要精确参数表示的情况下,校准是一个很好的例子)。 作为说明,并确保我们计算出正确的线,让我们在图像上绘制估计的线。 在这里,我们简单地绘制一个任意的黑色段,长度为 200 像素,厚度为 3 像素:
......@@ -464,7 +464,7 @@ The article by H.K. Yuen, J. Princen, J. Illingworth, and J Kittler, Comparative
将线拟合到一组点是数学中的经典问题。 OpenCV 的实现通过最小化每个点到线的距离之和来进行。 提出了几种距离函数,最快的选择是使用由`CV_DIST_L2`指定的欧几里德距离。 此选择对应于标准最小二乘法线拟合。 当离群点(即不属于该线的点)可能包括在点集中时,可以选择对远点影响较小的其他距离函数。 最小化基于 M 估计器技术,该技术迭代解决权重与距线的距离成反比的加权最小二乘问题。
使用此功能,还可以将线拟合到 3D 点集。 在这种情况下,输入为`cv::Point3i``cv::Point3f`的集合,而输出为`std::Vec6f`
使用此函数,还可以将线拟合到 3D 点集。 在这种情况下,输入为`cv::Point3i``cv::Point3f`的集合,而输出为`std::Vec6f`
## 更多
......@@ -487,7 +487,7 @@ The article by H.K. Yuen, J. Princen, J. Illingworth, and J Kittler, Comparative
## 操作步骤
OpenCV 提供了一个简单的功能,可以提取图像的已连接组件的轮廓。 它是`cv::findContours`函数:
OpenCV 提供了一个简单的函数,可以提取图像的已连接组件的轮廓。 它是`cv::findContours`函数:
```cpp
std::vector<std::vector<cv::Point>> contours;
......@@ -497,7 +497,7 @@ OpenCV 提供了一个简单的功能,可以提取图像的已连接组件的
CV_CHAIN_APPROX_NONE); // all pixels of each contours
```
输入显然是二进制图像。 输出是轮廓的向量,每个轮廓由`cv::Points`的向量表示。 这解释了为什么将输出参数定义为`std::vectors``std::vector`的原因。 另外,指定了两个标志。 第一个表示仅需要外部轮廓,也就是说,将忽略对象中的孔; (“更多”部分将讨论其他选项)。 那里的第二个标志指定轮廓的格式。 使用当前选项,向量将列出轮廓中的所有点。 使用标记`CV_CHAIN_APPROX_SIMPLE`,时,水平,垂直或对角线轮廓仅包含端点。 其他标记将给出轮廓的更复杂的链近似,以获得更紧凑的表示。 对于前面的图像,获得了`contours.size()`给出的九个轮廓。 幸运的是,有一个非常方便的功能可以在图像(这里是白色图像)上绘制这些轮廓:
输入显然是二进制图像。 输出是轮廓的向量,每个轮廓由`cv::Points`的向量表示。 这解释了为什么将输出参数定义为`std::vectors``std::vector`的原因。 另外,指定了两个标志。 第一个表示仅需要外部轮廓,也就是说,将忽略对象中的孔; (“更多”部分将讨论其他选项)。 那里的第二个标志指定轮廓的格式。 使用当前选项,向量将列出轮廓中的所有点。 使用标记`CV_CHAIN_APPROX_SIMPLE`,时,水平,垂直或对角线轮廓仅包含端点。 其他标记将给出轮廓的更复杂的链近似,以获得更紧凑的表示。 对于前面的图像,获得了`contours.size()`给出的九个轮廓。 幸运的是,有一个非常方便的函数可以在图像(这里是白色图像)上绘制这些轮廓:
```cpp
// Draw black contours on a white image
......@@ -508,7 +508,7 @@ OpenCV 提供了一个简单的功能,可以提取图像的已连接组件的
2); // with a thickness of 2
```
如果此功能的第三个参数为负值,则绘制所有轮廓。 否则,可以指定要绘制轮廓的索引。 结果显示在以下屏幕截图中:
如果此函数的第三个参数为负值,则绘制所有轮廓。 否则,可以指定要绘制轮廓的索引。 结果显示在以下屏幕截图中:
![How to do it...](img/3241OS_07_16.jpg)
......
......@@ -259,7 +259,7 @@ class HarrisDetector {
随着浮点处理器的出现,为避免特征值分解而引入的数学简化变得可以忽略不计,因此,可以基于显式计算的特征值进行哈里斯的检测。 原则上,此修改不应显着影响检测结果,但应避免使用任意`k`参数。
第二修改解决了特征点聚类的问题。 实际上,尽管引入了局部极大值条件,兴趣点仍倾向于在整个图像上分布不均,从而在高度纹理化的位置显示出浓度。 该问题的解决方案是在两个兴趣点之间施加最小距离。 这可以通过以下算法来实现。 从具有最强哈里斯分数的点(即具有最大最小特征值)开始,仅当兴趣点至少位于距已接受点的给定距离处时,才接受它们。 此解决方案在 OpenCV 中通过函数`cv::goodFeaturesToTrack`来实现,因为它检测到的功能可以用作视觉跟踪应用中的良好起点。 它被称为如下:
第二修改解决了特征点聚类的问题。 实际上,尽管引入了局部极大值条件,兴趣点仍倾向于在整个图像上分布不均,从而在高度纹理化的位置显示出浓度。 该问题的解决方案是在两个兴趣点之间施加最小距离。 这可以通过以下算法来实现。 从具有最强哈里斯分数的点(即具有最大最小特征值)开始,仅当兴趣点至少位于距已接受点的给定距离处时,才接受它们。 此解决方案在 OpenCV 中通过函数`cv::goodFeaturesToTrack`来实现,因为它检测到的特征可以用作视觉跟踪应用中的良好起点。 它被称为如下:
```cpp
// Compute good features to track
......@@ -395,7 +395,7 @@ The article by E. Rosten and T. Drummond, Machine learning for high-speed corner
## 操作步骤
SURF 特征的 OpenCV 实现也使用`cv::FeatureDetector`接口。 因此,这些功能的检测与我们在本章前面的秘籍中展示的类似:
SURF 特征的 OpenCV 实现也使用`cv::FeatureDetector`接口。 因此,这些特征的检测与我们在本章前面的秘籍中展示的类似:
```cpp
// vector of keypoints
......@@ -418,7 +418,7 @@ SURF 特征的 OpenCV 实现也使用`cv::FeatureDetector`接口。 因此,这
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); //flag
```
通过绘图功能生成的具有检测到的特征的最终图像为:
通过绘图函数生成的具有检测到的特征的最终图像为:
![How to do it...](img/3241OS_08_06.jpg)
......@@ -428,7 +428,7 @@ SURF 特征的 OpenCV 实现也使用`cv::FeatureDetector`接口。 因此,这
![How to do it...](img/3241OS_08_07.jpg)
通过仔细观察检测到的关键点,可以看出相应圆的大小变化与比例变化成比例。 例如,考虑右上方窗口的底部。 在两幅图像中,在该位置均检测到 SURF 特征,并且两个相应的圆圈(大小不同)包含相同的视觉元素。 当然,并非所有功能都如此,但是正如我们将在下一章中发现的那样,重复率足够高,可以在两个图像之间实现良好的匹配。
通过仔细观察检测到的关键点,可以看出相应圆的大小变化与比例变化成比例。 例如,考虑右上方窗口的底部。 在两幅图像中,在该位置均检测到 SURF 特征,并且两个相应的圆圈(大小不同)包含相同的视觉元素。 当然,并非所有特征都如此,但是正如我们将在下一章中发现的那样,重复率足够高,可以在两个图像之间实现良好的匹配。
## 工作原理
......@@ -452,7 +452,7 @@ SURF 通过以下步骤来实现此想法。 首先,为了检测特征,在
## 更多
SURF 算法已被开发为另一种著名的尺度不变特征检测器 SIFT(用于尺度不变特征变换)的有效变体。 SIFT 还可以将特征检测为图像和比例尺空间中的局部最大值,但使用 Laplacian 过滤器响应而不是 Hessian 行列式。 使用不同的高斯过滤器来计算不同比例的拉普拉斯算子。 OpenCV 有一个包装器类,用于检测这些功能,并且其调用方式与 SURF 特征类似:
SURF 算法已被开发为另一种著名的尺度不变特征检测器 SIFT(用于尺度不变特征变换)的有效变体。 SIFT 还可以将特征检测为图像和比例尺空间中的局部最大值,但使用 Laplacian 过滤器响应而不是 Hessian 行列式。 使用不同的高斯过滤器来计算不同比例的拉普拉斯算子。 OpenCV 有一个包装器类,用于检测这些特征,并且其调用方式与 SURF 特征类似:
```cpp
// vector of keypoints
......@@ -500,7 +500,7 @@ The pioneer work by D. Lowe, Distinctive Image Features from Scale Invariant Fea
结果是一个矩阵(即`cv::Mat`实例),它将包含与关键点向量中的元素数量一样多的行。 这些行中的每行都是一个 N 维描述符向量。 对于 SURF 描述符,默认情况下,其大小为 64。此向量表示特征点周围的强度模式。 两个特征点越相似,它们的描述符向量应该越接近。
这些描述符在图像匹配中特别有用。 例如,假设要对同一场景的两个图像进行匹配。 这可以通过首先检测每个图像上的特征,然后提取这些特征的描述符来完成。 然后将第一图像中的每个特征描述符向量与第二图像中的所有特征描述符进行比较。 然后将获得最佳分数(即,两个向量之间的最小距离)的对作为该特征的最佳匹配。 对第一张图片中的所有功能重复此过程。 这是已在 OpenCV 中实现为`cv::BruteForceMatcher`的最基本方案。 它的用法如下:
这些描述符在图像匹配中特别有用。 例如,假设要对同一场景的两个图像进行匹配。 这可以通过首先检测每个图像上的特征,然后提取这些特征的描述符来完成。 然后将第一图像中的每个特征描述符向量与第二图像中的所有特征描述符进行比较。 然后将获得最佳分数(即,两个向量之间的最小距离)的对作为该特征的最佳匹配。 对第一张图片中的所有特征重复此过程。 这是已在 OpenCV 中实现为`cv::BruteForceMatcher`的最基本方案。 它的用法如下:
```cpp
// Construction of the matcher
......@@ -512,7 +512,7 @@ The pioneer work by D. Lowe, Distinctive Image Features from Scale Invariant Fea
此类是`cv::DescriptorMatcher`类的子类,为不同的匹配策略定义了公共接口。 结果是`cv::DMatch`实例的向量,该向量是用来表示匹配对的结构。 本质上,`cv::DMatch`数据结构包含一个第一索引,该索引指向描述符的第一向量中的元素,以及一个第二索引,其指向描述符第二向量中的匹配特征。 它还包含一个表示两个匹配描述符之间距离的实数值。 此距离值用于比较两个`cv::DMatch`实例的`operator<`定义。
为了可视化匹配操作的结果,OpenCV 提供了一种绘制功能,该功能可以生成由两个输入图像连接而成的图像,并且在其上的匹配点由一条线链接。 在前面的秘籍中,我们为第一个图像获得了 340 个 SURF 点。 然后,暴力破解方法将产生相同数量的比赛。 在图像上绘制所有这些线会使结果不可读。 因此,我们将仅显示距离最小的 25 个匹配项。 通过使用`std::nth_element`可以轻松实现此目的,该工具将按排序顺序将第`n`个元素放置在第`n`个位置,而所有较小的元素都放置在该元素之前。 完成此操作后,将清除载体的剩余元素:
为了可视化匹配操作的结果,OpenCV 提供了一种绘制函数,该函数可以生成由两个输入图像连接而成的图像,并且在其上的匹配点由一条线链接。 在前面的秘籍中,我们为第一个图像获得了 340 个 SURF 点。 然后,暴力破解方法将产生相同数量的比赛。 在图像上绘制所有这些线会使结果不可读。 因此,我们将仅显示距离最小的 25 个匹配项。 通过使用`std::nth_element`可以轻松实现此目的,该工具将按排序顺序将第`n`个元素放置在第`n`个位置,而所有较小的元素都放置在该元素之前。 完成此操作后,将清除载体的剩余元素:
```cpp
std::nth_element(matches.begin(), // initial position
......@@ -566,4 +566,4 @@ SIFT 算法还定义了自己的描述符。 它基于在考虑的关键点的
## 另见
有关 SURF 和 SIFT 功能的更多信息,请参考前面的秘籍。
\ No newline at end of file
有关 SURF 和 SIFT 特征的更多信息,请参考前面的秘籍。
\ No newline at end of file
......@@ -45,7 +45,7 @@ OpenCV 建议使用棋盘图案来生成校准所需的 3D 场景点集。 该
![How to do it...](img/3241OS_09_03.jpg)
令人高兴的是,OpenCV 具有自动检测此棋盘图案角的功能。 您只需提供图像和所用棋盘的大小(垂直和水平内角点的数量)即可。 该函数将返回这些棋盘角在图像上的位置。 如果函数无法找到模式,则仅返回`false`
令人高兴的是,OpenCV 具有自动检测此棋盘图案角的函数。 您只需提供图像和所用棋盘的大小(垂直和水平内角点的数量)即可。 该函数将返回这些棋盘角在图像上的位置。 如果函数无法找到模式,则仅返回`false`
```cpp
// output vectors of image points
......@@ -57,7 +57,7 @@ OpenCV 建议使用棋盘图案来生成校准所需的 3D 场景点集。 该
boardSize, imageCorners);
```
请注意,如果需要调整算法,则此函数接受其他参数,此处不再讨论。 还有一个功能可以用棋盘上的线依次画出检测到的角:
请注意,如果需要调整算法,则此函数接受其他参数,此处不再讨论。 还有一个函数可以用棋盘上的线依次画出检测到的角:
```cpp
//Draw the corners
......@@ -194,7 +194,7 @@ double CameraCalibrator::calibrate(cv::Size &imageSize)
}
```
实际上,十至二十个棋盘图像就足够了,但是这些图像必须是从不同角度,不同深度拍摄的。 此功能的两个重要输出是相机矩阵和失真参数。 相机矩阵将在下一节中介绍。 现在,让我们考虑一下失真参数。 到目前为止,我们已经提到了使用针孔相机模型可以忽略镜头的影响。 但这只有在用于捕获图像的镜头不会引入太严重的光学畸变的情况下才有可能。 不幸的是,低质量的镜头或焦距非常短的镜头经常出现这种情况。 您可能已经注意到,在我们用于示例的图像中,所示的棋盘图案明显失真。 矩形板的边缘在图像中弯曲。 还应注意,随着我们远离图像中心,这种失真变得更加重要。 这是使用鱼眼镜头观察到的典型失真,称为**径向失真**。 普通数码相机中使用的镜头不会表现出如此高的畸变程度,但是在此处使用的镜头中,这些畸变肯定不能忽略。
实际上,十至二十个棋盘图像就足够了,但是这些图像必须是从不同角度,不同深度拍摄的。 此函数的两个重要输出是相机矩阵和失真参数。 相机矩阵将在下一节中介绍。 现在,让我们考虑一下失真参数。 到目前为止,我们已经提到了使用针孔相机模型可以忽略镜头的影响。 但这只有在用于捕获图像的镜头不会引入太严重的光学畸变的情况下才有可能。 不幸的是,低质量的镜头或焦距非常短的镜头经常出现这种情况。 您可能已经注意到,在我们用于示例的图像中,所示的棋盘图案明显失真。 矩形板的边缘在图像中弯曲。 还应注意,随着我们远离图像中心,这种失真变得更加重要。 这是使用鱼眼镜头观察到的典型失真,称为**径向失真**。 普通数码相机中使用的镜头不会表现出如此高的畸变程度,但是在此处使用的镜头中,这些畸变肯定不能忽略。
通过引入适当的模型可以补偿这些变形。 这个想法是用一组数学方程来表示由透镜引起的畸变。 一旦建立,这些方程式然后可以被还原以便消除图像上可见的失真。 幸运的是,可以在校准阶段与其他相机参数一起获得将校正失真的变换的确切参数。 完成此操作后,来自新校准相机的任何图像都可以保持不失真:
......@@ -393,7 +393,7 @@ class RobustMatcher {
}
```
注意我们如何使用通用的`cv::FeatureDetector``cv::DescriptorExtractor`接口,以便用户可以提供任何特定的实现。 默认情况下,此处使用`SURF`函数和描述符,但可以使用适当的设置器方法指定其他功能
注意我们如何使用通用的`cv::FeatureDetector``cv::DescriptorExtractor`接口,以便用户可以提供任何特定的实现。 默认情况下,此处使用`SURF`函数和描述符,但可以使用适当的设置器方法指定其他函数
```cpp
// Set the feature detector
......@@ -472,7 +472,7 @@ class RobustMatcher {
}
```
第一步只是检测特征点并计算其描述符。 接下来,我们像上一章一样使用`cv::BruteForceMatcher`类进行特征匹配。 但是,这一次,我们为每个功能找到了两个最佳匹配点(而不仅仅是我们在前面的秘籍中所做的最佳匹配点)。 这是通过`cv::BruteForceMatcher::knnMatch`方法(`k = 2`)完成的。 此外,我们在两个方向上执行此匹配,即,对于第一张图像中的每个点,我们在第二张图像中找到两个最佳匹配,然后对第二张图像的特征点执行相同的操作,在第一张图片中找到它们的两个最佳匹配。
第一步只是检测特征点并计算其描述符。 接下来,我们像上一章一样使用`cv::BruteForceMatcher`类进行特征匹配。 但是,这一次,我们为每个特征找到了两个最佳匹配点(而不仅仅是我们在前面的秘籍中所做的最佳匹配点)。 这是通过`cv::BruteForceMatcher::knnMatch`方法(`k = 2`)完成的。 此外,我们在两个方向上执行此匹配,即,对于第一张图像中的每个点,我们在第二张图像中找到两个最佳匹配,然后对第二张图像的特征点执行相同的操作,在第一张图片中找到它们的两个最佳匹配。
因此,对于每个特征点,我们在另一个视图中都有两个候选匹配。 根据描述符之间的距离,这是最好的两个。 如果此测量距离对于最佳匹配而言非常低,而对于第二最佳匹配而言则更大,则我们可以放心地将第一匹配视为好匹配,因为它无疑是最佳选择。 相反,如果两个最佳匹配的距离相对较近,则选择一个或另一个可能会出错。 在这种情况下,我们应该拒绝两个匹配项。 在这里,我们通过验证最佳匹配的距离与第二最佳匹配的距离之比不大于给定阈值来执行此测试:
......
......@@ -20,7 +20,7 @@
## 操作步骤
基本上,要读取视频序列的帧,您要做的就是创建`cv::VideoCapture`类的实例。 然后,您创建一个循环,该循环将提取并读取每个视频帧。 这是一个基本的主要功能,可以简单地显示视频序列的帧:
基本上,要读取视频序列的帧,您要做的就是创建`cv::VideoCapture`类的实例。 然后,您创建一个循环,该循环将提取并读取每个视频帧。 这是一个基本的`main`函数,可以简单地显示视频序列的帧:
```cpp
int main()
......@@ -101,7 +101,7 @@ capture.grab();
capture.retrieve(frame);
```
还请注意,在我们的示例中,如何在显示每一帧时引入延迟。 使用`cv::waitKey`函数完成此操作。 在这里,我们将延迟设置为与输入视频帧速率相对应的值(如果`fps`是每秒的帧数,则 1000/fps 是两帧之间的延迟,以 ms 为单位)。 您显然可以更改此值以较慢或较快的速度显示视频。 但是,如果要显示视频帧,则要确保窗口有足够的刷新时间,插入这样的延迟很重要(由于这是低优先级的过程,因此,如果 CPU 太忙)。 `cv::waitKey`函数还允许我们通过按任意键来中断读取过程。 在这种情况下,函数将返回所按下键的 ASCII 码。 请注意,如果指定给`cv::waitKey`函数的延迟为 0,则它​​将无限期地等待用户按下某个键。 当某人想通过逐帧检查结果来跟踪过程时,此功能非常有用。
还请注意,在我们的示例中,如何在显示每一帧时引入延迟。 使用`cv::waitKey`函数完成此操作。 在这里,我们将延迟设置为与输入视频帧速率相对应的值(如果`fps`是每秒的帧数,则 1000/fps 是两帧之间的延迟,以 ms 为单位)。 您显然可以更改此值以较慢或较快的速度显示视频。 但是,如果要显示视频帧,则要确保窗口有足够的刷新时间,插入这样的延迟很重要(由于这是低优先级的过程,因此,如果 CPU 太忙)。 `cv::waitKey`函数还允许我们通过按任意键来中断读取过程。 在这种情况下,函数将返回所按下键的 ASCII 码。 请注意,如果指定给`cv::waitKey`函数的延迟为 0,则它​​将无限期地等待用户按下某个键。 当某人想通过逐帧检查结果来跟踪过程时,此函数非常有用。
最后一条语句调用`release`方法,该方法将关闭视频文件。 但是,由于`release`也由`cv::VideoCapture`析构器调用,因此不需要此调用。
......@@ -121,17 +121,17 @@ capture.retrieve(frame);
# 处理视频帧
在本秘籍中,我们的目标是对视频序列的每个帧应用某种处理功能。 为此,我们将 OpenCV 视频捕获框架封装到我们自己的类中。 除其他事项外,此类可让我们指定每次提取新帧时都会调用的函数。
在本秘籍中,我们的目标是对视频序列的每个帧应用某种处理函数。 为此,我们将 OpenCV 视频捕获框架封装到我们自己的类中。 除其他事项外,此类可让我们指定每次提取新帧时都会调用的函数。
## 操作步骤
我们想要的是能够指定在视频序列的每个帧处调用的处理功能(*回调*函数)。 可以将此功能定义为接收`cv::Mat`实例并输出已处理的帧。 因此,我们将其设计为具有以下签名:
我们想要的是能够指定在视频序列的每个帧处调用的处理函数(*回调*函数)。 可以将此函数定义为接收`cv::Mat`实例并输出已处理的帧。 因此,我们将其设计为具有以下签名:
```cpp
void processFrame(cv::Mat& img, cv::Mat& out);
```
作为此类处理功能的示例,请考虑以下用于计算输入图像的 Canny 边缘的简单函数:
作为此类处理函数的示例,请考虑以下用于计算输入图像的 Canny 边缘的简单函数:
```cpp
void canny(cv::Mat& img, cv::Mat& out) {
......@@ -468,7 +468,7 @@ class VideoProcessor {
### 使用帧处理器类
在面向对象的上下文中,使用框架处理类代替框架处理功能可能更有意义。 实际上,一个类将使程序员在定义视频处理算法时具有更大的灵活性。 因此,我们可以定义一个接口,希望在`VideoProcessor`内部使用的任何类都需要实现:
在面向对象的上下文中,使用框架处理类代替框架处理函数可能更有意义。 实际上,一个类将使程序员在定义视频处理算法时具有更大的灵活性。 因此,我们可以定义一个接口,希望在`VideoProcessor`内部使用的任何类都需要实现:
```cpp
// The frame processor interface
......@@ -497,7 +497,7 @@ class FrameProcessor {
}
```
指定帧处理器类实例后,它将使之前可能已经设置的任何帧处理功能失效。 现在,如果指定了帧处理功能,则同样适用:
指定帧处理器类实例后,它将使之前可能已经设置的任何帧处理函数失效。 现在,如果指定了帧处理函数,则同样适用:
```cpp
// set the callback function that will
......@@ -800,7 +800,7 @@ Codec : XVID
## 操作步骤
要开始跟踪过程,首先要做的是检测初始帧中的特征点。 然后,您尝试在下一帧中跟踪这些点。 您必须找到这些点现在在此新框架中的位置。 显然,由于我们正在处理视频序列,因此很有可能在其上找到了特征点的对象已经移动(该运动也可能是由于摄像机的运动引起的)。 因此,必须在一个点的先前位置附近搜索,以便在下一帧中找到它的新位置。 这就是完成`cv::calcOpticalFlowPyrLK`函数的功能。 您在第一张图像中输入两个连续的帧和一个特征点向量,该函数将返回新点位置的向量。 要跟踪完整序列上的点,请逐帧重复此过程。 请注意,当您沿着序列中的点进行跟踪时,不可避免地会失去对其中某些点的跟踪,从而跟踪的特征点的数量将逐渐减少。 因此,不时检测新功能可能是一个好主意。
要开始跟踪过程,首先要做的是检测初始帧中的特征点。 然后,您尝试在下一帧中跟踪这些点。 您必须找到这些点现在在此新框架中的位置。 显然,由于我们正在处理视频序列,因此很有可能在其上找到了特征点的对象已经移动(该运动也可能是由于摄像机的运动引起的)。 因此,必须在一个点的先前位置附近搜索,以便在下一帧中找到它的新位置。 这就是完成`cv::calcOpticalFlowPyrLK`函数的功能。 您在第一张图像中输入两个连续的帧和一个特征点向量,该函数将返回新点位置的向量。 要跟踪完整序列上的点,请逐帧重复此过程。 请注意,当您沿着序列中的点进行跟踪时,不可避免地会失去对其中某些点的跟踪,从而跟踪的特征点的数量将逐渐减少。 因此,不时检测新特征可能是一个好主意。
现在,我们将利用先前秘籍中定义的框架,并定义一个类,该类实现在本章“处理视频帧”秘籍中引入的`FrameProcessor`接口。 此类的数据属性包括执行特征点检测及其跟踪所需的变量:
......@@ -946,7 +946,7 @@ class FeatureTracker : public FrameProcessor {
}
```
然后将编写一个简单的主要功能来跟踪视频序列中的特征点,如下所示:
然后将编写一个简单的`main`函数来跟踪视频序列中的特征点,如下所示:
```cpp
int main()
......@@ -974,7 +974,7 @@ int main()
}
```
生成的程序将显示跟踪的功能随时间的变化。 例如,这是两个不同时刻的两个这样的帧。 在此视频中,摄像机是固定的。 因此,年轻的自行车手是唯一的运动对象。 这是视频开头的一帧:
生成的程序将显示跟踪的特征随时间的变化。 例如,这是两个不同时刻的两个这样的帧。 在此视频中,摄像机是固定的。 因此,年轻的自行车手是唯一的运动对象。 这是视频开头的一帧:
![How to do it...](img/3241OS_10_05.jpg)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册