利用轮廓检测,可以检测出目标的边界,并在图像中方便地定位目标。它通常是许多有趣应用的第一步,如图像前景提取,简单的图像分割,检测和识别。
因此,让我们学习使用OpenCV的轮廓和轮廓检测,并自己看看如何使用它们来构建各种应用程序。
已经存使用轮廓进行运动检测或分割的应用程序。下面是一些例子:
运动检测
:在监控视频中,运动检测技术有许多应用,包括室内和室外的安全环境、交通控制、体育活动中的行为检测、无人看管的物体检测,甚至视频压缩。在下面的图中,我们可以看到在视频流中检测人的移动在监控应用中是多么有用。请注意,静止在图像左侧的一组人是如何不被检测到的。无人看管的对象检测
:公共场所任何无人看管的物体通常被认为是可疑物体。一种有效而安全的解决方案是:(利用背景消除法以及轮廓检测实现无人值勤目标检测)。前景\背景分割
:要用另一幅图像替换背景,需要执行图像前景提取(类似于图像分割)。使用轮廓是一种可以用于执行分割的方法。下面的图片展示了这样一个应用程序的简单示例:当我们把物体边界上的所有点连接起来时,就得到一条轮廓线。通常,特定的轮廓是指具有相同颜色和强度的边界像素。OpenCV使得在图像中寻找和绘制轮廓变得非常容易。它提供了两个简单的功能:
同时,它有两种不同的轮廓检测算法:
既然已经介绍了轮廓,现在让我们讨论检测轮廓所涉及的步骤。
OpenCV使这一任务变得相当简单。只需遵循以下步骤:
1.读取图像并将其转换为灰度格式
:读取图像并将图像转换为灰度格式。将图像转换为灰度是非常重要的,因为它为下一步准备图像。将图像转换为单通道灰度图像是实现阈值化的重要步骤,而阈值化是轮廓检测算法正常工作的必要条件。2.应用二值化
:在寻找轮廓时,首先对灰度图像进行二值化或Canny边缘检测。在这里,我们将应用二值化。3.找到轮廓
:使用findContours()
函数检测图像中的轮廓。4.在原始RGB图像上绘制轮廓
:一旦轮廓被确定,使用drawContours()函数将轮廓覆盖在原始RGB图像上。(1)Python
# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image = cv2.imread('0.jpg')
# 将图像转换为灰度格式
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)
# 显示
cv2.imshow('Binary image', thresh)
cv2.waitKey(0)
cv2.imwrite('image_thres1.jpg', thresh)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 读取
Mat image = imread("0.jpg");
// 将图像转换为灰度格式
Mat img_gray;
cvtColor(image, img_gray, COLOR_BGR2GRAY);
// 二值化
Mat thresh;
threshold(img_gray, thresh, 150, 255, THRESH_BINARY);
imshow("Binary mage", thresh);
waitKey(0);
imwrite("image_thres1.jpg", thresh);
destroyAllWindows();
现在,让我们使用CHAIN_APPROX_NONE
方法找到并绘制轮廓。
从findContours()
函数开始。它有三个必需参数,如下所示。
image
:上一步获取的二值输入图像。mode
:这是轮廓检索模式。我们使用RETR_TREE,这意味着该算法将从二值图像中检索所有可能的轮廓。更多的轮廓检索模式是可用的,我们也将讨论它们。method
:这定义了轮廓近似法。在本例中,我们将使用CHAIN_APPROX_NONE
。虽然略慢于CHAIN_APPROX_SIMPLE
,但我们将在这里使用这个方法来存储所有轮廓点。这里需要强调的是,模式是指要检索的轮廓的类型,而方法是指存储轮廓中的哪些点。我们将在下面对此进行更详细的讨论。
接下来,使用drawContours()
函数在RGB图像上覆盖轮廓。这个函数有四个必选参数和几个可选参数。下面的前四个参数是必需的。
image
:这是你想在上面画轮廓的输入RGB图像。contours
:从findContours()函数获得的轮廓contourIdx
:在得到的所有轮廓中列出轮廓索引。使用此参数,您可以指定此列表中的索引位置,准确指示要绘制的轮廓。提供一个负值将绘制所有轮廓。color
:这表示要绘制的轮廓的颜色。我们用绿色标出这些轮廓。thickness
:这是轮廓的厚度。(1)Python
# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image = cv2.imread('0.jpg')
# 将图像转换为灰度格式
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)
# 显示
cv2.imshow('Binary image', thresh)
cv2.waitKey(0)
# 使用cv2.CHAIN_APPROX_NONE检测二值图像上的轮廓。
contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# 在原始图像上绘制轮廓
image_copy = image.copy()
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# 显示结果
cv2.imshow('None approximation', image_copy)
cv2.waitKey(0)
# cv2.imwrite('contours_none_image1.jpg', image_copy)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 读取
Mat image = imread("0.jpg");
// 将图像转换为灰度格式
Mat img_gray;
cvtColor(image, img_gray, COLOR_BGR2GRAY);
// 二值化
Mat thresh;
threshold(img_gray, thresh, 150, 255, THRESH_BINARY);
imshow("Binary mage", thresh);
waitKey(0);
// 使用cv2.CHAIN_APPROX_NONE检测二值图像上的轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(thresh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
// 在原始图像上绘制轮廓
Mat image_copy = image.clone();
drawContours(image_copy, contours, -1, Scalar(0, 255, 0), 2);
imshow("None approximation", image_copy);
waitKey(0);
//imwrite("contours_none_image1.jpg", image_copy);
destroyAllWindows();
正如上图所示,算法生成的轮廓很好地识别了每个对象的边界。然而,如果你仔细看手机,你会发现它包含不止一个轮廓线。对于与相机镜头和光线相关的圆形区域,已经确定了单独的轮廓。沿着手机边缘的部分也有“次级”轮廓。
请记住,轮廓算法的准确性和质量很大程度上取决于所提供的二值图像的质量(再次查看上一节中的二值图像,您可以看到与这些次级轮廓相关的线)。有些应用需要高质量的轮廓。在这种情况下,在创建二值图像时,使用不同的阈值进行实验,看看是否会改善结果轮廓。
在轮廓生成之前,还可以使用其他方法来消除二值图中不需要的轮廓。您还可以使用与我们将在这里讨论的轮廓算法相关的更高级的特征。
下面是分别使用红色、绿色和蓝色通道检测轮廓时的一些结果。我们在前面的轮廓检测步骤中讨论过这个问题。下面是与上面相同的图像的Python和c++代码。
(1)Python
import cv2
# 读取图像
image = cv2.imread('input/image_1.jpg')
# B, G, R 通道分离
blue, green, red = cv2.split(image)
# 使用蓝色通道和无阈值检测轮廓
contours1, hierarchy1 = cv2.findContours(image=blue, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# 在原始图像上绘制轮廓
image_contour_blue = image.copy()
cv2.drawContours(image=image_contour_blue, contours=contours1, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# 查看结果
cv2.imshow('Contour detection using blue channels only', image_contour_blue)
cv2.waitKey(0)
cv2.imwrite('blue_channel.jpg', image_contour_blue)
cv2.destroyAllWindows()
# 使用绿色通道和无阈值检测轮廓
contours2, hierarchy2 = cv2.findContours(image=green, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# 在原始图像上绘制轮廓
image_contour_green = image.copy()
cv2.drawContours(image=image_contour_green, contours=contours2, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# 查看结果
cv2.imshow('Contour detection using green channels only', image_contour_green)
cv2.waitKey(0)
cv2.imwrite('green_channel.jpg', image_contour_green)
cv2.destroyAllWindows()
# 使用红色通道和无阈值检测轮廓
contours3, hierarchy3 = cv2.findContours(image=red, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
# 在原始图像上绘制轮廓
image_contour_red = image.copy()
cv2.drawContours(image=image_contour_red, contours=contours3, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# 查看结果
cv2.imshow('Contour detection using red channels only', image_contour_red)
cv2.waitKey(0)
cv2.imwrite('red_channel.jpg', image_contour_red)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main() {
// 读取图片
Mat image = imread("input/image_1.jpg");
// B, G, R 通道分离
Mat channels[3];
split(image, channels);
// 使用蓝色通道检测轮廓而不使用阈值
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(channels[0], contours1, hierarchy1, RETR_TREE, CHAIN_APPROX_NONE);
// 在原始图像上绘制轮廓
Mat image_contour_blue = image.clone();
drawContours(image_contour_blue, contours1, -1, Scalar(0, 255, 0), 2);
imshow("Contour detection using blue channels only", image_contour_blue);
waitKey(0);
imwrite("blue_channel.jpg", image_contour_blue);
destroyAllWindows();
// 使用绿色通道和无阈值检测轮廓
vector<vector<Point>> contours2;
vector<Vec4i> hierarchy2;
findContours(channels[1], contours2, hierarchy2, RETR_TREE, CHAIN_APPROX_NONE);
// 在原始图像上绘制轮廓
Mat image_contour_green = image.clone();
drawContours(image_contour_green, contours2, -1, Scalar(0, 255, 0), 2);
imshow("Contour detection using green channels only", image_contour_green);
waitKey(0);
imwrite("green_channel.jpg", image_contour_green);
destroyAllWindows();
// 使用红色通道和无阈值检测轮廓
vector<vector<Point>> contours3;
vector<Vec4i> hierarchy3;
findContours(channels[2], contours3, hierarchy3, RETR_TREE, CHAIN_APPROX_NONE);
// 在原始图像上绘制轮廓
Mat image_contour_red = image.clone();
drawContours(image_contour_red, contours3, -1, Scalar(0, 255, 0), 2);
imshow("Contour detection using red channels only", image_contour_red);
waitKey(0);
imwrite("red_channel.jpg", image_contour_red);
destroyAllWindows();
}
从上图中我们可以看出,轮廓检测算法并不能很好地找到轮廓。这是因为它不能正确地检测物体的边界,也没有很好地定义像素之间的强度差。这就是为什么我们更喜欢使用灰度和二值阈值图像来检测轮廓。
(1)Python
# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image = cv2.imread('0.jpg')
# 将图像转换为灰度格式
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)
# 显示
cv2.imshow('Binary image', thresh)
cv2.waitKey(0)
# 使用cv2.CHAIN_APPROX_NONE检测二值图像上的轮廓。
contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_SIMPLE)
# 在原始图像上绘制轮廓
image_copy = image.copy()
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
# 显示结果
cv2.imshow('None approximation', image_copy)
cv2.waitKey(0)
# cv2.imwrite('contours_none_image1.jpg', image_copy)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 读取
Mat image = imread("0.jpg");
// 将图像转换为灰度格式
Mat img_gray;
cvtColor(image, img_gray, COLOR_BGR2GRAY);
// 二值化
Mat thresh;
threshold(img_gray, thresh, 150, 255, THRESH_BINARY);
imshow("Binary mage", thresh);
waitKey(0);
// 使用cv2.CHAIN_APPROX_NONE检测二值图像上的轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(thresh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
// 在原始图像上绘制轮廓
Mat image_copy = image.clone();
drawContours(image_copy, contours, -1, Scalar(0, 255, 0), 2);
imshow("None approximation", image_copy);
waitKey(0);
//imwrite("contours_none_image1.jpg", image_copy);
destroyAllWindows();
CHAIN_APPROX_SIMPLE
算法压缩沿轮廓的水平、垂直和对角线段,只留下它们的端点。这意味着沿着直线路径的任何点都将被忽略,只剩下端点。例如,考虑一个矩形的轮廓。除四个角点外,所有轮廓点将被剔除。这种方法比CHAIN_APPROX_NONE
更快,因为算法不存储所有的点,使用更少的内存,因此执行时间更少。
为什么呢?
功劳归于drawContours()函数。虽然CHAIN_APPROX_SIMPLE
方法通常会导致更少的点,但drawContours()
函数会自动连接相邻的点,将它们连接起来,即使它们不在轮廓列表中。
那么,我们如何确认CHAIN_APPROX_SIMPLE
算法实际上是有效的呢?
下面的代码使用上面的图像来可视化CHAIN_APPROX_SIMPLE
算法的效果。除了两个额外的for循环和一些变量名之外,几乎所有内容都与前面的代码示例相同。
(1)Python
# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
# 为了真正可视化“CHAIN_APPROX_SIMPLE”的效果,我们需要一个合适的图像
image1 = cv2.imread('new.jpg')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours2, hierarchy2 = cv2.findContours(thresh1, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
image_copy2 = image1.copy()
cv2.drawContours(image_copy2, contours2, -1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow('SIMPLE Approximation contours', image_copy2)
cv2.waitKey(0)
image_copy3 = image1.copy()
for i, contour in enumerate(contours2): # 遍历轮廓
for j, contour_point in enumerate(contour): # 遍历轮廓点
# 在当前轮廓点坐标上画一个圆
cv2.circle(image_copy3, ((contour_point[0][0], contour_point[0][1])), 2, (0, 255, 0), 2, cv2.LINE_AA)
# 可视化
cv2.imshow('CHAIN_APPROX_SIMPLE Point only', image_copy3)
cv2.waitKey(0)
cv2.imwrite('contour_point_simple.jpg', image_copy3)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 使用合适的图像来可视化CHAIN APPROX SIMPLE
Mat image1 = imread("new.jpg");
Mat img_gray1;
cvtColor(image1, img_gray1, COLOR_BGR2GRAY);
Mat thresh1;
threshold(img_gray1, thresh1, 150, 255, THRESH_BINARY);
vector<vector<Point>> contours2;
vector<Vec4i> hierarchy2;
findContours(thresh1, contours2, hierarchy2, RETR_TREE, CHAIN_APPROX_NONE);
Mat image_copy2 = image1.clone();
drawContours(image_copy2, contours2, -1, Scalar(0, 255, 0), 2);
imshow("None approximation", image_copy2);
waitKey(0);
imwrite("contours_none_image1.jpg", image_copy2);
destroyAllWindows();
Mat image_copy3 = image1.clone();
for(int i=0; i<contours2.size(); i=i+1){
for (int j=0; j<contours2[i].size(); j=j+1){
circle(image_copy3, (contours2[i][0], contours2[i][1]), 2, Scalar(0, 255, 0), 2);
}
}
imshow("CHAIN_APPROX_SIMPLE Point only", image_copy3);
waitKey(0);
imwrite("contour_point_simple.jpg", image_copy3);
destroyAllWindows();
}
使用CHAIN_APPROX_SIMPLE进行轮廓检测时,可以看到书的四个角上只有四个轮廓点。书中垂直和水平的直线完全被忽略了。
观察输出图像,它在上图的右手边。请注意,书的垂直和水平边只包含四个角。还要注意字母和鸟是用离散的点而不是线段表示的。
层次表示轮廓之间的父子关系。您将看到每种轮廓检索模式如何影响图像中的轮廓检测,并产生分层结果。
图像中轮廓检测算法检测到的物体可以是:
在大多数情况下,当一个形状包含更多的形状时,我们可以有把握地得出结论,外部形状是内部形状的父形状。
看一下下图,它包含了几个简单的形状,可以帮助演示轮廓层次结构。
图像与简单的线条和形状。我们将使用这张图片来学习更多关于轮廓层次结构的知识。现在请看下图,与上图中的每个形状相关联的轮廓已经被识别出来了。下图中的每个数字都有重要意义。
您已经看到findContours()
函数返回两个输出:contours
列表和层次结构。现在让我们详细了解轮廓层次结构输出。
轮廓层次结构表示为数组,数组又包含四个值。它表示为:[Next, Previous, First_Child, Parent]
那么,所有这些值意味着什么?
Next
:表示图像中的下一个轮廓,处于同一层次。所以,
Next
是2。Previous
:在同一层次上的前一个轮廓。这意味着轮廓1的Previous
值始终为-1。
First_Child
:表示我们当前考虑的轮廓的第一个子轮廓。
First_Child
的索引值将是-1。First_Child
的位置值将是索引位置3a。Parent
:表示当前轮廓的父轮廓索引位置。
上面的解释是有意义的,但是我们如何实际可视化这些层次结构数组呢?最好的方法是:
到目前为止,我们使用了一种特定的检索技术RETR TREE
来查找和绘制轮廓,但是OpenCV中还有另外三种轮廓检索技术,即RETR LIST、RETR EXTERNAL和RETR CCOMP
。
因此,现在让我们使用上图中的图像来检查这四个方法中的每一个,以及它们的相关代码来获得轮廓。
RETR_LIST
轮廓检索方法不会在提取的轮廓之间创建任何父-子关系。因此,对于所有检测到的轮廓区域,First_Child
和Parent
索引位置值始终为-1。# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image1 = cv2.imread('simple.png')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
ret, thresh1= cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours1, hierarchy1 = cv2.findContours(thresh1, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
image_copy1 = image1.copy()
cv2.drawContours(image_copy1, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)
# 显示
cv2.imshow('LIST', image_copy1)
print(f"LIST: {
hierarchy1}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_list.jpg', image_copy1)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 使用合适的图像来可视化
Mat image1 = imread('simple.png');
Mat img_gray1;
cvtColor(image1, img_gray1, COLOR_BGR2GRAY);
Mat thresh1;
threshold(img_gray1, thresh1, 150, 255, THRESH_BINARY);
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(thresh1, contours1, hierarchy1, RETR_LIST, CHAIN_APPROX_NONE);
Mat image_copy1 = image1.clone();
drawContours(image_copy1, contours1, -1, Scalar(0, 255, 0), 2);
imshow("LIST", image_copy1);
waitKey(0);
imwrite("contours_retr_list.jpg", image_copy1);
destroyAllWindows();
}
可以清楚地看到,所有检测到的轮廓区域的第3和第4个索引位置都是-1,正如预期的那样。
RETR_EXTERNAL
轮廓检索方法是一种非常有趣的方法。它只检测父轮廓,而忽略任何子轮廓。所有的内轮廓,比如3a和4上面都没有点。# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image1 = cv2.imread('simple.png')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
ret, thresh1= cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours1, hierarchy1 = cv2.findContours(thresh1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
image_copy1 = image1.copy()
cv2.drawContours(image_copy1, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)
# 显示
cv2.imshow('EXTERNAL', image_copy1)
print(f"EXTERNAL: {
hierarchy1}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_external.jpg', image_copy1)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 使用合适的图像来可视化
Mat image1 = imread('simple.png');
Mat img_gray1;
cvtColor(image1, img_gray1, COLOR_BGR2GRAY);
Mat thresh1;
threshold(img_gray1, thresh1, 150, 255, THRESH_BINARY);
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(thresh1, contours1, hierarchy1, RETR_EXTERNAL, CHAIN_APPROX_NONE);
Mat image_copy1 = image1.clone();
drawContours(image_copy1, contours1, -1, Scalar(0, 255, 0), 2);
imshow("EXTERNAL", image_copy1);
waitKey(0);
imwrite("contours_retr_external.jpg", image_copy1);
destroyAllWindows();
}
上面的输出图像只显示了在轮廓1、2和3上绘制的点。轮廓3a和4省略,因为它们是子轮廓。
RETR_CCOMP
与RETR_EXTERNAL
不同,RETR_CCOMP
检索图像中的所有轮廓。除此之外,它还对图像中的所有形状或对象应用2级层次结构。
但是如果我们有一个轮廓在另一个层次2级轮廓内部呢?就像轮廓4在3a内部。
# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image1 = cv2.imread('simple.png')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
ret, thresh1= cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours1, hierarchy1 = cv2.findContours(thresh1, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
image_copy1 = image1.copy()
cv2.drawContours(image_copy1, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)
# 显示
cv2.imshow('CCOMP', image_copy1)
print(f"CCOMP: {
hierarchy1}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_ccomp.jpg', image_copy1)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 使用合适的图像来可视化
Mat image1 = imread('simple.png');
Mat img_gray1;
cvtColor(image1, img_gray1, COLOR_BGR2GRAY);
Mat thresh1;
threshold(img_gray1, thresh1, 150, 255, THRESH_BINARY);
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(thresh1, contours1, hierarchy1, RETR_CCOMP, CHAIN_APPROX_NONE);
Mat image_copy1 = image1.clone();
drawContours(image_copy1, contours1, -1, Scalar(0, 255, 0), 2);
imshow("EXTERNAL", image_copy1);
// cout << "EXTERNAL:" << hierarchy1;
waitKey(0);
imwrite("contours_retr_ccomp.jpg", image_copy1);
destroyAllWindows();
}
在这里,我们看到,根据轮廓检索方法,所有的Next、Previous、First Child和Parent关系都得到了维护,因为所有的轮廓都被检测到了。如预期,第一个轮廓区域的Previous为-1。而没有任何父元素的轮廓,值也是-1
RETR_CCOMP
一样,RETR_TREE
也检索所有的轮廓。它还创建了一个完整的层次结构,级别不限于1或2。每个轮廓可以有自己的层次结构,与它所处的级别一致,以及它所具有的相应的父子关系。(1)Python
# 首先导入OpenCV,并读取输入图像。
import cv2
# 读取图像
image1 = cv2.imread('simple.png')
img_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
ret, thresh1= cv2.threshold(img_gray1, 150, 255, cv2.THRESH_BINARY)
contours1, hierarchy1 = cv2.findContours(thresh1, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
image_copy1 = image1.copy()
cv2.drawContours(image_copy1, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)
# 显示
cv2.imshow('TREE', image_copy1)
print(f"TREE: {
hierarchy1}")
cv2.waitKey(0)
cv2.imwrite('contours_retr_tree.jpg', image_copy1)
cv2.destroyAllWindows()
(2)C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
// 使用合适的图像来可视化
Mat image1 = imread('simple.png');
Mat img_gray1;
cvtColor(image1, img_gray1, COLOR_BGR2GRAY);
Mat thresh1;
threshold(img_gray1, thresh1, 150, 255, THRESH_BINARY);
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(thresh1, contours1, hierarchy1, RETR_TREE, CHAIN_APPROX_NONE);
Mat image_copy1 = image1.clone();
drawContours(image_copy1, contours1, -1, Scalar(0, 255, 0), 2);
imshow("EXTERNAL", image_copy1);
// cout << "EXTERNAL:" << hierarchy1;
waitKey(0);
imwrite("contours_retr_tree.jpg", image_copy1);
destroyAllWindows();
}
所有的轮廓都按照预期绘制,轮廓区域清晰可见。你还推断轮廓3和3a是两个独立的轮廓,因为它们有不同的轮廓边界和区域。同时,很明显轮廓3a是轮廓3的产物。
仅仅知道轮廓检索方法是不够的。您还应该了解它们的相对处理时间。下表比较了上面讨论的每个方法的运行时。
比较了不同方法的推理速度
从上表中可以得出一些有趣的结论:
RETR_LIST
和RETR_EXTERNAL
的执行时间最少,因为RETR_LIST
没有定义任何层次结构,而RETR_EXTERNAL
只检索父轮廓RETR_CCOMP
的执行时间是第二快的。它检索所有轮廓并定义一个两级层次结构。RETR_TREE
执行时间最长,因为它检索了所有的轮廓,并为每个父子关系定义了独立的层次结构级别。虽然上述时间可能看起来不重要,但重要的是要意识到可能需要大量轮廓处理的应用程序的差异。同样值得注意的是,这个处理时间可能会有所不同,这取决于它们提取的轮廓的程度以及它们定义的层次级别。
到目前为止,我们探索的所有例子似乎都很有趣,结果也令人鼓舞。然而,在某些情况下,轮廓算法可能无法提供有意义和有用的结果。让我们也考虑这样一个例子。
您从轮廓检测开始,并学习在OpenCV中实现它。了解了应用程序如何使用轮廓来进行移动检测和分割。接下来,我们演示了四种不同的检索模式和两种轮廓近似方法的使用。你还学会了画轮廓。最后,我们讨论了轮廓层次结构,以及不同的轮廓检索模式对图像轮廓绘制的影响。
关键点:
https://learnopencv.com/contour-detection-using-opencv-python-c/