OpenCV|OpenCV基础(13)基于OpenCV的轮廓检测
【OpenCV|OpenCV基础(13)基于OpenCV的轮廓检测】利用轮廓检测,可以检测出目标的边界,并在图像中方便地定位目标。它通常是许多有趣应用的第一步,如图像前景提取,简单的图像分割,检测和识别。
因此,让我们学习使用OpenCV的轮廓和轮廓检测,并自己看看如何使用它们来构建各种应用程序。
1.轮廓在计算机视觉中的应用 已经存使用轮廓进行运动检测或分割的应用程序。下面是一些例子:
运动检测
:在监控视频中,运动检测技术有许多应用,包括室内和室外的安全环境、交通控制、体育活动中的行为检测、无人看管的物体检测,甚至视频压缩。在下面的图中,我们可以看到在视频流中检测人的移动在监控应用中是多么有用。请注意,静止在图像左侧的一组人是如何不被检测到的。
文章图片
无人看管的对象检测
:公共场所任何无人看管的物体通常被认为是可疑物体。一种有效而安全的解决方案是:(利用背景消除法以及轮廓检测实现无人值勤目标检测)。
文章图片
前景\背景分割
:要用另一幅图像替换背景,需要执行图像前景提取(类似于图像分割)。使用轮廓是一种可以用于执行分割的方法。下面的图片展示了这样一个应用程序的简单示例:
文章图片
- findContours()
- drawContours()
- CHAIN_APPROX_SIMPLE
- CHAIN_APPROX_NONE
3.OpenCV中轮廓检测和绘制的步骤 OpenCV使这一任务变得相当简单。只需遵循以下步骤:
1.读取图像并将其转换为灰度格式
:读取图像并将图像转换为灰度格式。将图像转换为灰度是非常重要的,因为它为下一步准备图像。将图像转换为单通道灰度图像是实现阈值化的重要步骤,而阈值化是轮廓检测算法正常工作的必要条件。2.应用二值化
:在寻找轮廓时,首先对灰度图像进行二值化或Canny边缘检测。在这里,我们将应用二值化。
这将图像转换为黑白,突出感兴趣的目标,使轮廓检测算法更容易。阈值化使图像中物体的边界完全变白,所有像素具有相同的强度。该算法现在可以从这些白色像素中检测出物体的边界。
注意:值为0的黑色像素被视为背景像素而忽略。
在这一点上,可能会出现一个问题。如果我们使用单一通道,如R(红色),G(绿色),或B(蓝色),而不是灰度(阈值)图像会怎样?在这种情况下,轮廓检测算法就不能很好地工作。如前所述,该算法寻找边界和类似强度的像素来检测轮廓。二值图像提供的信息比单一(RGB)彩色通道图像要好得多。在博客的后面部分,我们得到了仅使用单个R、G或B通道而不是灰度和阈值图像时的结果图像。3.找到轮廓
:使用findContours()
函数检测图像中的轮廓。4.在原始RGB图像上绘制轮廓
:一旦轮廓被确定,使用drawContours()函数将轮廓覆盖在原始RGB图像上。
# 首先导入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();
文章图片
文章图片
5.使用CHAIN_APPROX_NONE绘制轮廓 现在,让我们使用
CHAIN_APPROX_NONE
方法找到并绘制轮廓。从
findContours()
函数开始。它有三个必需参数,如下所示。image
:上一步获取的二值输入图像。mode
:这是轮廓检索模式。我们使用RETR_TREE,这意味着该算法将从二值图像中检索所有可能的轮廓。更多的轮廓检索模式是可用的,我们也将讨论它们。method
:这定义了轮廓近似法。在本例中,我们将使用CHAIN_APPROX_NONE
。虽然略慢于CHAIN_APPROX_SIMPLE
,但我们将在这里使用这个方法来存储所有轮廓点。
接下来,使用
drawContours()
函数在RGB图像上覆盖轮廓。这个函数有四个必选参数和几个可选参数。下面的前四个参数是必需的。image
:这是你想在上面画轮廓的输入RGB图像。contours
:从findContours()函数获得的轮廓contourIdx
:在得到的所有轮廓中列出轮廓索引。使用此参数,您可以指定此列表中的索引位置,准确指示要绘制的轮廓。提供一个负值将绘制所有轮廓。color
:这表示要绘制的轮廓的颜色。我们用绿色标出这些轮廓。thickness
:这是轮廓的厚度。
# 首先导入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 contours;
vector 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();
文章图片
正如上图所示,算法生成的轮廓很好地识别了每个对象的边界。然而,如果你仔细看手机,你会发现它包含不止一个轮廓线。对于与相机镜头和光线相关的圆形区域,已经确定了单独的轮廓。沿着手机边缘的部分也有“次级”轮廓。
请记住,轮廓算法的准确性和质量很大程度上取决于所提供的二值图像的质量(再次查看上一节中的二值图像,您可以看到与这些次级轮廓相关的线)。有些应用需要高质量的轮廓。在这种情况下,在创建二值图像时,使用不同的阈值进行实验,看看是否会改善结果轮廓。
在轮廓生成之前,还可以使用其他方法来消除二值图中不需要的轮廓。您还可以使用与我们将在这里讨论的轮廓算法相关的更高级的特征。
6.使用单通道:红色、绿色或蓝色 下面是分别使用红色、绿色和蓝色通道检测轮廓时的一些结果。我们在前面的轮廓检测步骤中讨论过这个问题。下面是与上面相同的图像的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 contours1;
vector 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 contours2;
vector 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 contours3;
vector 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();
}
文章图片
从上图中我们可以看出,轮廓检测算法并不能很好地找到轮廓。这是因为它不能正确地检测物体的边界,也没有很好地定义像素之间的强度差。这就是为什么我们更喜欢使用灰度和二值阈值图像来检测轮廓。
7.使用CHAIN_APPROX_SIMPLE绘制轮廓 (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 contours;
vector 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
算法实际上是有效的呢?- 最直接的方法是手动循环轮廓点,并使用OpenCV在检测到的轮廓坐标上画一个圆。
- 同时,我们使用不同的图像来帮助我们可视化算法的结果。
CHAIN_APPROX_SIMPLE
算法的效果。除了两个额外的for循环和一些变量名之外,几乎所有内容都与前面的代码示例相同。- 第一个for循环遍历在轮廓列表中的每个轮廓区域。
- 第二个循环是遍历在这个区域的每个坐标。
- 然后我们使用OpenCV的circle()函数在每个坐标点上画一个绿色的圆。
- 最后,我们可视化结果并将其保存到磁盘中。
# 首先导入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 contours2;
vector 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
文章图片
使用CHAIN_APPROX_SIMPLE进行轮廓检测时,可以看到书的四个角上只有四个轮廓点。书中垂直和水平的直线完全被忽略了。
观察输出图像,它在上图的右手边。请注意,书的垂直和水平边只包含四个角。还要注意字母和鸟是用离散的点而不是线段表示的。
8.轮廓层次结构 层次表示轮廓之间的父子关系。您将看到每种轮廓检索模式如何影响图像中的轮廓检测,并产生分层结果。
(1)亲子关系
图像中轮廓检测算法检测到的物体可以是:
- 在图像中分散的单个物体(如第一个例子),或
- 物体和形状在彼此内部
看一下下图,它包含了几个简单的形状,可以帮助演示轮廓层次结构。
文章图片
图像与简单的线条和形状。我们将使用这张图片来学习更多关于轮廓层次结构的知识。现在请看下图,与上图中的每个形状相关联的轮廓已经被识别出来了。下图中的每个数字都有重要意义。
- 根据轮廓层次和父子关系,所有的个体数字,即1、2、3、4,都是独立的对象。
- 我们可以说 3a 是 3 的孩子。 注意 3a 代表 3 的内部部分。
- 轮廓1、2和4都是父形状,没有任何关联的子形状,因此它们的编号是任意的。换句话说,轮廓2可以被标记为1,反之亦然。
文章图片
您已经看到
findContours()
函数返回两个输出:contours
列表和层次结构。现在让我们详细了解轮廓层次结构输出。轮廓层次结构表示为数组,数组又包含四个值。它表示为:
[Next, Previous, First_Child, Parent]
那么,所有这些值意味着什么?
Next
:表示图像中的下一个轮廓,处于同一层次。所以,- 对于轮廓1,同一层次下一个轮廓为2。这里,
Next
是2。 - 因此,轮廓3没有与它本身相同层次的下一个轮廓。所以下一个值是-1。
Previous
:在同一层次上的前一个轮廓。这意味着轮廓1的Previous
值始终为-1。First_Child
:表示我们当前考虑的轮廓的第一个子轮廓。- 轮廓1和2根本没有子轮廓。因此,
First_Child
的索引值将是-1。 - 但是轮廓3有个子轮廓。因此,对于轮廓3,
First_Child
的位置值将是索引位置3a。
Parent
:表示当前轮廓的父轮廓索引位置。- 很明显,轮廓1和2没有任何父轮廓。
- 对于3a轮廓,它的父轮廓是3
- 轮廓4的父轮廓是轮廓3a
- 使用一个有线条和形状组成的简单图像
- 使用不同的检索模式检测轮廓和层次结构
- 然后打印这些值以可视化它们
到目前为止,我们使用了一种特定的检索技术
RETR TREE
来查找和绘制轮廓,但是OpenCV中还有另外三种轮廓检索技术,即RETR LIST、RETR EXTERNAL和RETR CCOMP
。文章图片
因此,现在让我们使用上图中的图像来检查这四个方法中的每一个,以及它们的相关代码来获得轮廓。
- RETR_LIST
RETR_LIST
轮廓检索方法不会在提取的轮廓之间创建任何父-子关系。因此,对于所有检测到的轮廓区域,First_Child
和Parent
索引位置值始终为-1。
如上所述,所有轮廓都有相应的上一轮廓和下一轮廓。
(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_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 contours1;
vector 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
RETR_EXTERNAL
轮廓检索方法是一种非常有趣的方法。它只检测父轮廓,而忽略任何子轮廓。所有的内轮廓,比如3a和4上面都没有点。
(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_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 contours1;
vector 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级层次结构。
- 所有的外部轮廓将有层次1级
- 所有的内部轮廓将有层次2级
- 同样,轮廓4将有层次1级。
- 如果轮廓4内有轮廓,则此轮廓等级为2。
在下面的图像中,轮廓已经根据其层次级别进行了编号,如上所述。
文章图片
上图显示了第1级和第2级的层次级别分别为HL-1和HL-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_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 contours1;
vector 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_TREE
就像RETR_CCOMP
一样,RETR_TREE
也检索所有的轮廓。它还创建了一个完整的层次结构,级别不限于1或2。每个轮廓可以有自己的层次结构,与它所处的级别一致,以及它所具有的相应的父子关系。
文章图片
从上图可以看出: - 轮廓 1、2 和 3 处于同一级别,即级别 0
- 轮廓3a等级1,因为它是轮廓3的子轮廓。
- 轮廓4是一种新的轮廓区域,其等级为2。
# 首先导入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 contours1;
vector 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的产物。
(4)不同轮廓检索方法的运行时比较
仅仅知道轮廓检索方法是不够的。您还应该了解它们的相对处理时间。下表比较了上面讨论的每个方法的运行时。
文章图片
比较了不同方法的推理速度
从上表中可以得出一些有趣的结论:
RETR_LIST
和RETR_EXTERNAL
的执行时间最少,因为RETR_LIST
没有定义任何层次结构,而RETR_EXTERNAL
只检索父轮廓RETR_CCOMP
的执行时间是第二快的。它检索所有轮廓并定义一个两级层次结构。RETR_TREE
执行时间最长,因为它检索了所有的轮廓,并为每个父子关系定义了独立的层次结构级别。
9.局限性 到目前为止,我们探索的所有例子似乎都很有趣,结果也令人鼓舞。然而,在某些情况下,轮廓算法可能无法提供有意义和有用的结果。让我们也考虑这样一个例子。
- 当图像中的物体与它们的背景形成强烈对比时,你可以清楚地识别出与每个物体相关的轮廓。但是,如果您有一个图像,如下面的图所示,该怎么办呢?它不仅有一个明亮的对象(小狗),而且还有一个与感兴趣的对象(小狗)相同值(亮度)的背景。你会发现右图中的轮廓甚至不完整。此外,在背景区域,有多个不需要的轮廓。
文章图片
- 当图像中物体的背景充满了线条时,轮廓检测也可能失败。
关键点:
- OpenCV中的轮廓检测算法在图像有黑色背景和明确的感兴趣目标时工作得非常好。
- 但是,当输入图像的背景是杂乱的,或者与感兴趣的目标像素强度相同时,算法的效果就不那么好了。
推荐阅读
- 基于微信小程序带后端ssm接口小区物业管理平台设计
- 基于|基于 antd 风格的 element-table + pagination 的二次封装
- opencv|opencv C++模板匹配的简单实现
- Java|Java OpenCV图像处理之SIFT角点检测详解
- Python基础|Python基础 - 练习1
- 基于爱,才会有“愿望”当“要求”。2017.8.12
- Java|Java基础——数组
- Java基础-高级特性-枚举实现状态机
- 营养基础学20180331(课间随笔)??
- iOS面试题--基础