本文介绍了一种使用YOLOv8.2和OpenCV从图像中提取所有检测到的对象并将它们保存为单独文件的简单方法。
目录
介绍
示例图像
使用 YOLOv8 检测物体
解析检测结果
结论
目录
引言
示例图像
使用YOLOv8检测物体
YOLOv8模型详解
运行模型检测物体 解析
检测结果
提取带背景的物体
提取不带背景的物体
结论
介绍
在本教程中,我将展示如何使用神经网络检测图像中的所有对象,提取它们并保存到单独的文件中。
这是一个常见的任务,有很多不同的方法可以实现。在本文中,我将展示一种非常简单的方法,使用 YOLOv8 神经网络和 OpenCV。
本教程仅涵盖此主题,因此,如果您想深入了解 YOLOv8 神经网络和计算机视觉,请阅读我的 YOLOv8 系列之前的文章。
本文所有代码均使用 Python 编写,因此我假设您具备 Python 开发能力。此外,我使用的是 Jupyter Notebook,但这并非必需。您可以使用任何 IDE 或文本编辑器来编写和运行代码。
示例图像
在本教程中,我们将使用 YOLOv8 神经网络和 OpenCV 从图像中检测和提取目标。我们将使用以下图片作为示例,该图片来自维基百科页面:
来源:https://en.wikipedia.org/wiki/Vehicular_cycling
我们将检测图像中的所有人物和车辆,并将它们提取出来保存到单独的图像文件中。我将演示如何保存带背景或不带背景的提取对象。您可以根据需要选择第一种或第二种方法。
使用 YOLOv8 检测物体
那么,我们开始吧。首先,你需要安装 YOLOv8.2 软件包(如果你还没有安装的话)。为此,请在 Jupyter notebook 中运行以下命令:
%pip install ultralytics
然后导入YOLOv8 API:
from ultralytics import YOLO
完成后,我们来加载YOLOv8神经网络模型:
model = YOLO("yolov8m-seg.pt")
这行代码会下载yolov8m-seg.pt神经网络模型并将其加载到model变量中。
关于不同YOLOv8型号的更多信息
在本教程中,我们将使用预训练的YOLOv8模型之一,该模型可用于检测80种常见物体类别。YOLOv8模型共有三种类型和五种不同规模。
| 分类 | 检测 | 分割 | 种类 |
| yolov8n-cls.pt | yolov8n.pt | yolov8n-seg.pt | 纳米 |
| yolov8s-cls.pt | yolov8s.pt | yolov8s-seg.pt | 小的 |
| yolov8m-cls.pt | yolov8m.pt | yolov8m-seg.pt | 中等的 |
| yolov8l-cls.pt | yolov8l.pt | yolov8l-seg.pt | 大的 |
| yolov8x-cls.pt | yolov8x.pt | yolov8x-seg.pt | 巨大的 |
你选择的型号越大,得到的结果质量就越高,但运行速度就越慢。
YOLOv8模型分为三种类型:分类模型、目标检测模型和实例分割模型。分类模型仅用于检测图像中的目标类别,因此不适用于我们的任务。目标检测模型可以检测目标的边界框。这些模型可以获取每个目标的x1、y1、x2、y2坐标,并可利用这些坐标提取目标及其背景。最后,目标检测模型segmentation models不仅可以检测目标的边界框,还可以检测目标的精确形状(边界多边形)。利用边界多边形,可以提取不包含背景的目标。
在上面的代码中,我加载了中等大小的分割模型yolov8m-seg.pt,该模型既可以用于提取带背景的对象,也可以用于提取不带背景的对象。
要检测预训练模型中不存在的特定对象类别,您可以创建并训练自己的模型,将其保存到.pt文件中并加载。请阅读我的 YOLOv8 系列文章的第一部分,了解具体操作方法。
运行模型以检测物体
要检测图像中的对象,您可以将图像文件名列表传递给该model对象,并接收每个图像的结果数组:
results = model(["road.jpg"])
这段代码假设示例图像已保存到road.jpg文件中。如果您只发送列表中的单个图像,则results数组将包含一个元素。您可以发送多个图像,在这种情况下,结果将包含更多元素。
解析检测结果
现在,让我们获取第一张图像的检测结果(road.jpg)
result = results[0]
这是一个ultralytics.engine.results.Resultsresult类的对象,其中包含有关图像上检测到的对象的不同信息。
您可以使用上面的链接了解此对象包含的所有方法和属性,但这里我们只需要其中几个:
-
result.boxes.xyxy- 图像上检测到的所有对象的边界框数组。 -
result.masks.xy- 图像中检测到的所有对象的边界多边形数组。
例如,它将result.boxes.xyxy[0]包含图像上检测到的第一个对象的 [x1,y1,x2,y2] 坐标:
print(result.boxes.xyxy[0])
tensor([2251.1409, 1117.8158, 3216.7141, 1744.1128], device='cuda:0')
以及同一对象的边界多边形:
print(result.masks.xy[0])
[[ 2500 1125]
[ 2493.8 1131.2]
[ 2481.2 1131.2]
[ 2475 1137.5]
[ 2468.8 1137.5]
[ 2462.5 1143.8]
[ 2456.2 1143.8]
[ 2418.8 1181.2]
[ 2418.8 1187.5]
[ 2381.2 1225]
[ 2381.2 1231.2]
[ 2350 1262.5]
[ 2350 1268.8]
[ 2337.5 1281.2]
[ 2337.5 1287.5]
[ 2325 1300]
[ 2325 1306.2]
[ 2306.2 1325]
[ 2306.2 1331.2]
[ 2300 1337.5]
[ 2300 1343.8]
[ 2287.5 1356.2]
[ 2287.5 1362.5]
[ 2281.2 1368.8]
[ 2281.2 1387.5]
[ 2275 1393.8]
[ 2275 1700]
[ 2281.2 1706.2]
[ 2281.2 1712.5]
[ 2287.5 1718.8]
[ 2356.2 1718.8]
[ 2368.8 1706.2]
[ 2368.8 1700]
[ 2381.2 1687.5]
[ 2381.2 1681.2]
[ 2393.8 1668.8]
[ 2393.8 1662.5]
[ 2412.5 1643.8]
[ 2456.2 1643.8]
[ 2462.5 1650]
[ 2468.8 1650]
[ 2481.2 1662.5]
[ 2562.5 1662.5]
[ 2568.8 1656.2]
[ 2575 1656.2]
[ 2581.2 1650]
[ 2712.5 1650]
[ 2718.8 1656.2]
[ 2737.5 1656.2]
[ 2743.8 1662.5]
[ 2768.8 1662.5]
[ 2775 1668.8]
[ 2831.2 1668.8]
[ 2837.5 1675]
[ 2868.8 1675]
[ 2875 1681.2]
[ 2887.5 1681.2]
[ 2900 1693.8]
[ 2906.2 1693.8]
[ 2912.5 1700]
[ 2918.8 1700]
[ 2931.2 1712.5]
[ 2931.2 1718.8]
[ 2937.5 1718.8]
[ 2950 1731.2]
[ 2956.2 1731.2]
[ 2962.5 1737.5]
[ 3018.8 1737.5]
[ 3018.8 1731.2]
[ 3037.5 1712.5]
[ 3037.5 1706.2]
[ 3043.8 1700]
[ 3043.8 1681.2]
[ 3050 1675]
[ 3050 1668.8]
[ 3056.2 1662.5]
[ 3062.5 1662.5]
[ 3068.8 1668.8]
[ 3081.2 1668.8]
[ 3100 1687.5]
[ 3106.2 1687.5]
[ 3112.5 1693.8]
[ 3175 1693.8]
[ 3181.2 1687.5]
[ 3187.5 1687.5]
[ 3193.8 1681.2]
[ 3193.8 1662.5]
[ 3200 1656.2]
[ 3200 1562.5]
[ 3193.8 1556.2]
[ 3193.8 1500]
[ 3187.5 1493.8]
[ 3187.5 1468.8]
[ 3181.2 1462.5]
[ 3181.2 1437.5]
[ 3175 1431.2]
[ 3175 1418.8]
[ 3168.8 1412.5]
[ 3168.8 1400]
[ 3143.8 1375]
[ 3143.8 1368.8]
[ 3125 1350]
[ 3125 1343.8]
[ 3112.5 1331.2]
[ 3112.5 1325]
[ 3100 1312.5]
[ 3100 1306.2]
[ 3087.5 1293.8]
[ 3087.5 1287.5]
[ 3075 1275]
[ 3075 1268.8]
[ 3068.8 1262.5]
[ 3068.8 1256.2]
[ 3050 1237.5]
[ 3050 1231.2]
[ 3006.2 1187.5]
[ 3006.2 1181.2]
[ 3000 1175]
[ 3000 1168.8]
[ 2993.8 1162.5]
[ 2993.8 1156.2]
[ 2987.5 1150]
[ 2987.5 1143.8]
[ 2975 1143.8]
[ 2968.8 1137.5]
[ 2950 1137.5]
[ 2943.8 1131.2]
[ 2868.8 1131.2]
[ 2862.5 1125]]
这是多边形内所有点的 [x,y] 坐标列表。
您可以在下方看到第一个检测到的对象的边界框和边界多边形:
| 边界框 | 边界多边形 |
![]() |
![]() |
正如你可能猜到的那样,要提取带有背景的对象,你可以使用边界框,但要提取不带背景的对象,你需要使用边界多边形。
要提取所有对象并保存到单独的文件中,需要循环运行代码来处理检测到的每个对象。
接下来的章节中,我将展示如何实现这两个目标。
提取带有背景的对象
首先,我将演示如何使用边界框坐标裁剪单个对象。然后,我们将编写一个循环来提取所有检测到的对象。
在上一节中,我们提取了第一个检测到的物体的边界框result.boxes.xyxy[0]。它包含一个 [x1,y1,x2,y2] 数组,其中包含坐标。然而,这是一个 PyTorch 张量,其值为 Float32 类型,但坐标必须是整数。让我们将张量转换为合适的坐标:
x1,y1,x2,y2 = result.boxes.xyxy[0].cpu().numpy().astype(int);
现在,你需要加载图像并使用上面的坐标对其进行裁剪。
我将使用 OpenCV 库。请确保您的系统已安装该库,或者安装它:
%pip install opencv-python
然后导入并加载图像:
import cv2
img = cv2.imread("road.jpg")
OpenCV 生成的图像是一个标准的NumPy数组。您可以看到它的形状:
print(img.shape)
(604, 800, 3)
第一个维度是行数(图像的高度),第二个维度是列数(图像的宽度),第三个维度是颜色通道数,对于标准 RGB 图像,该值为 3。
现在,利用我们已知的 x1、y1、x2、y2 坐标,很容易裁剪出该数组的这部分:
img[y1:y2,x1:x2,:]
这样就只得到了y1到y2行和x1到x2列的数据,也就是我们所需要的对象。让我们把裁剪后的图像保存到一个新文件中:
cv2.imwrite("image1.png",img[y1:y2,x1:x2,:])
就这样。运行这段代码后,你应该会看到一个新文件image1.png。打开它,你应该会看到带有背景的裁剪后的对象:
现在,你可以编写一个循环,提取并保存所有检测到的对象:
for idx,box in enumerate(result.boxes.xyxy):
x1,y1,x2,y2 = box.cpu().numpy().astype(int)
cv2.imwrite(f"image{idx}.png", img[y1:y2,x1:x2,:])
运行此程序后,您将看到包含图像上所有检测到的对象的文件image0.png,等等。image1.png
这是一个完整的解决方案:
from ultralytics import YOLO
import cv2
model = YOLO("yolov8m-seg.pt")
results = model(["road.jpg"])
result = results[0]
img = cv2.imread("road.jpg")
for idx,box in enumerate(result.boxes.xyxy):
x1,y1,x2,y2 = box.cpu().numpy().astype(int)
cv2.imwrite(f"image{idx}.png", img[y1:y2,x1:x2,:])
提取无背景的对象
首先,要使图像透明,我们需要向输入图像添加透明度通道。默认情况下,JPG 图像没有此通道,因此,要使用 OpenCV 添加它,您需要运行以下代码行:
img = cv2.cvtColor(img,cv2.COLOR_BGR2BGRA)
如上一节所述,使用矩形区域裁剪图像很容易,但我没有找到任何可以使用自定义多边形裁剪图像的 Python 库。因此,我们将采用另一种方法。首先,我们将整个输入图像中所有不在边界多边形内的像素设为透明,然后,我们将使用边界框从这个透明图像中裁剪出目标对象,就像我们在上一节中所做的那样。
为了实现第一部分(使图像透明),我们需要创建并应用一个二值掩码到图像上。二值掩码是一张黑白图像,其中所有白色像素都被视为属于对象的像素,所有黑色像素都被视为透明像素。例如,包含第一个对象的图像的二值掩码如下所示:
OpenCV 有一个函数,可以将二值掩码应用于图像,此操作会使图像上的所有像素透明,但二值掩码中为白色的像素除外。
但首先,我们需要为 OpenCV 创建一个二值掩码:黑色图像加上白色边界多边形。
正如我上面所说,OpenCV 图像是一个 NumPy 数组,因此,要创建一个黑色二值图像,你需要创建一个与原始图像大小相同的 NumPy 数组,并用 0 填充。
import numpy as np
mask = np.zeros_like(img,dtype=np.int32)
这段代码创建了一个与原始图像大小相同的数组road.jpg,数组中所有元素均为 0。该图像中元素的数据类型必须为整数。现在,你需要绘制一个白色边界多边形,使其看起来与之前图像中的二值掩码相同。
第一个位于该区域内的对象的边界多边形result.masks.xy[0]。此多边形中的项目类型为float32,但对于图像,它们必须是int32。要将多边形转换为正确的类型,请使用以下代码:
polygon = result.masks.xy[0].astype(np.int32)
现在,fillPoly可以使用 OpenCV 库的函数在黑色掩模上绘制白色多边形:
cv2.fillPoly(mask,[polygon],color=(255, 255, 255))
最后,我们使用 OpenCV 的二进制 AND 运算,将图像中除目标物体以外的所有部分都变为透明:
img = cv2.bitwise_and(img, img, mask=mask[:,:,0].astype('uint8'))
它使用掩码对图像中的每个像素应用二进制与运算img。因此,掩码值为 0 的图像像素将变为透明。
操作完成后,您将得到以下图像:
最后,您可以像上一节中那样,使用边界框坐标从中裁剪出对象:
x1,y1,x2,y2 = result.boxes.xyxy[0].cpu().numpy().astype(int)
cv2.imwrite("image1.png",img[y1:y2,x1:x2,:])
运行此命令后,该image1.png文件将包含对象,但不包含背景:
现在,让我们从图像中提取所有检测到的对象。为此,我们需要对每个检测到的对象重复执行本节中的所有代码:
for idx,polygon in enumerate(result.masks.xy):
polygon = polygon.astype(np.int32)
img = cv2.imread("road.jpg")
img = cv2.cvtColor(img,cv2.COLOR_BGR2BGRA)
mask = np.zeros_like(img,dtype=np.int32)
cv2.fillPoly(mask,[polygon],color=(255, 255, 255))
x1,y1,x2,y2 = result.boxes.xyxy[idx].cpu().numpy().astype(int)
img = cv2.bitwise_and(img, img, mask=mask[:,:,0].astype('uint8'))
cv2.imwrite(f"image{idx}.png", img[y1:y2,x1:x2,:])
运行这段代码后,你应该会看到图像文件image0.png,等等。每张图片都将具有透明背景image1.png。image2.png
以下是使用 YOLOv8.2 和 OpenCV 从透明背景图像中提取所有对象并将其保存到文件的完整解决方案:
from ultralytics import YOLO
import cv2
import numpy as np
model = YOLO("yolov8m-seg.pt")
results = model(["road.jpg"])
result = results[0]
for idx,polygon in enumerate(result.masks.xy):
polygon = polygon.astype(np.int32)
img = cv2.imread("road.jpg")
img = cv2.cvtColor(img,cv2.COLOR_BGR2BGRA)
mask = np.zeros_like(img,dtype=np.int32)
cv2.fillPoly(mask,[polygon],color=(255, 255, 255))
x1,y1,x2,y2 = result.boxes.xyxy[idx].cpu().numpy().astype(int)
img = cv2.bitwise_and(img, img, mask=mask[:,:,0].astype('uint8'))
cv2.imwrite(f"image{idx}.png", img[y1:y2,x1:x2,:])
结论
本文展示了一种使用 YOLOv8 和 OpenCV 从图像中提取并保存所有检测到的物体的简单方法,代码量不到 20 行。本文并未深入探讨细节。如果您想了解更多关于计算机视觉和 YOLOv8 的知识,欢迎阅读我之前撰写的 YOLOv8 系列文章。您可以在本文开头或结尾找到相关链接。
您可以在LinkedIn、Twitter和Facebook上关注我,以便第一时间了解像这样的新文章和其他软件开发新闻。
享受编程的乐趣,永不止步地学习!
文章来源:https://dev.to/andreygermanov/how-to-extract-all-Detected-objects-from-image-and-save-them-as-separate-images-using-yolov82-and-opencv-3j99






