用Blender制作YOLO目标检测器训练数据

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景

本文将介绍一种非常有吸引力的机器学习训练数据的替代方案,用于为给定的特定应用程序收集数据。 无论应用程序类型如何,这篇博文都旨在向读者展示使用 Blender 等开源资源生成合成数据(Synthetic Data)的潜力。

事实上,对于现实生活中的数据而言,在短时间内收集数据并对其进行标记的需求尚未得到完全解决。 这就是为什么,为了解决这个问题,我们必须转向合成数据生成,通过适量的代码,它可以提供稍后训练深度学习模型所需的标签和特征。

在本例中,我们专注于对象识别问题,并使用 Blender 及其脚本功能生成数据。

1、项目概况

对于这个项目,我们将生成数据来识别下图中所示的木制玩具。 为了做到这一点,我们将创建一种算法,以与图片中相同的配置拍摄所有对象的照片,并输出与每个图像中每个对象的位置的边界框相对应的标签。
在这里插入图片描述

我们希望识别的类如下:

    Pink flowerBlue squareGreen starYellow hexagonOrange lozengePink ovalBlue rectangleGreen circleOrange triangle

上述算法将在渲染软件Blender中用Python实现。 Blender 是一款开源软件,用于从动画到产品设计等多种渲染应用程序。 该软件将允许创建上面看到的对象的真实渲染,同时也允许我们访问每个对象的位置,这是完成标记的关键功能。

本文的 Blender 文件、整个代码和所有必要的资源都可以在这里下载。

2、Blender场景设置

无论你想要识别哪个对象,为了生成合成数据来训练其识别器,我们都必须在 Blender 中表示这个或这些对象。 因此,我们必须创建和设置一个与实际场景最相似的场景,我们通常会在其中找到我们想要识别的对象。

为了解释如何做到这一点,在本节中,我们将引导读者完成设置与自动生成数据的脚本兼容的场景所需的主要步骤。

2.1 CAD模型导入

第一步包括将我们想要识别的对象导入(或创建)到 Blender 中。 在我们的例子中,我们决定在 Catia V5 中创建 CAD 模型,然后将其导入为 .stl。 也可以在 Blender 中从头开始创建模型,但是,由于我们更熟悉 Catia,因此我们决定在该软件上执行此操作。

在这里插入图片描述

Catia 环境中包含我们要识别的对象的集合

然后我们继续打开 Blender 开始导入我们想要识别的每个 .stl 对象。 我们单击“文件”窗口,然后单击“导入”,最后选择“Stl(.stl)”选项,以导入之前从 Catia V5 导出的模型。

在这里插入图片描述

Blender STL 文件导入

在完成 STL 导入之前,我们必须指定比例为 0.001,因为 Blender 的工作单位为米,而 Catia 的工作单位为毫米。 这使我们模型的单位缩放到 Blender 参考系统,因此在后期阶段,与导入的模型相比,相机具有成比例的尺寸。

在这里插入图片描述

在 Blender 中导入之前缩放 STL 模型

将每个对象导入 Blender 环境后,请确保创建一个平面来描绘场景的表面。 另外,请确保命名“场景集合”菜单中的每个对象,这些对象可以在下图中的右侧面板中看到。 确保这些名称易于书写、简短,并且如果名称包含两个单词,则用下划线连接。

在这里插入图片描述

所有对象均已导入并命名的最终场景

2.2 场景定义

导入模型后,我们将继续定义整个场景,以使其看起来最真实。 场景越真实,我们的训练数据就越好,我们的算法就越能识别我们训练它检测的现实生活中的物体。

因此,我们首先通过选择每个对象并进入右侧面板中的“材质”选项来定义每个对象的材质,如下所示。 调整对象外观的三个关键参数是基色、镜面反射和粗糙度。 需要调整后两者来定义物体是吸收还是反射光,是有光泽的还是无光泽的。
在这里插入图片描述

Blender 对象材质定义面板

此外,如果需要,可以将纹理添加到场景中的对象。 这些纹理可以从图像中导入,并从图像中施加基色。 对于我们的项目,纹理被添加到地板和容纳物体的平台上。

在这里插入图片描述

将纹理指定给 Blender 对象

接下来,定义灯光,可以通过复制并粘贴第一个灯光来创建第二个灯光,该灯光是在 Blender 场景启动时自动创建的。 至于对象,请确保为这些灯光分配名称,例如 light1 和 light2,以便你在进入脚本部分后可以在代码中轻松调用它们。

这两个灯光最重要的参数是“功率”参数,一旦我们进入脚本部分,它将使我们能够调整灯光的强度。 如下图,右下角,我们可以看到“灯光属性”面板打开,功率参数为16W。

在这里插入图片描述

Blender场景及灯光

定义了所有材质并添加了适合目的的灯光后,我们可以通过单击下图中用绿色圈出的右上角按钮来继续可视化渲染。 这允许我们在渲染模式下访问视口着色。

在这里插入图片描述

渲染后的对象

现在场景搭建完毕!

2.3 相机设置

设置 Blender 场景以开始编写脚本的最后一步是设置相机! 这一步实际上至关重要,因为我们需要能够通过脚本轻松控制相机,使其移动并拍照。 我们决定让相机围绕物体旋转,从各个角度拍照。

然而,如果我们考虑相机设置移动的立方空间 (x,y,z),这种运动就不那么明显了。 这意味着设置定义描述对象周围球形运动的 (x,y,z) 点列表会太复杂。 因此,为了简化这项任务,我们决定创建一个固定在场景中心的轴,相机移动时将遵循该轴。 这意味着当轴在中心旋转时,相机将随之旋转。 想象一下,将你的肘部放在桌子上并移动你的拳头,拳头围绕你的肘部旋转。 现在想象一下,肘部是轴,拳头是相机,相机绕轴旋转。

因此,我们创建一个轴并将其放置在场景的中心,如下所示。

在这里插入图片描述

在这里插入图片描述
在 Blender 中将轴添加到场景中心

现在,为了使相机的移动服从轴的移动,我们按住 Shift 键并首先选择相机,然后选择轴。 一旦它们都被选择,我们点击 Ctr + P 并选择对象(保持变换)。 这将使该轴成为相机的父轴。


将轴设置为相机的父级

你可以继续测试这是否有效,方法是进入相机视图,选择轴并进入下图中以绿色突出显示的变换窗口,然后更改轴的旋转坐标。 当轴旋转坐标改变时,你会看到相机绕物体旋转。

在这里插入图片描述

相机绕物体运行的演示

3、Blender Python脚本

现在完整的场景已经设置完毕,我们可以开始编写Python脚本了。 此 Blender 功能将使我们能够自动生成渲染,以便制作数以万计的图片和标签,这些图片和标签将用作我们的对象识别算法的训练数据。 这是非常强大的,因为这意味着如果我们有一个足够真实的 Blender 场景,我们可以在大约两天内生成多达 20000 个图像和标签(这个时间将根据你自己机器的 GPU 的容量而变化)。

可以通过单击下图中橙色下划线的脚本窗口来访问此脚本功能。 通过单击此窗口,我们将出现三个主要元素:以黄色显示的 Blender 控制台、以橙色显示的脚本环境以及以绿色显示的命令跟踪器。

在这里插入图片描述

脚本窗口及其主要组件

命令跟踪器允许你跟踪在 Blender 环境中修改特定参数时正在使用的实际脚本命令。 例如,在下图中我修改了Axis的位置。

在这里插入图片描述

跟踪用于更改轴位置的脚本

脚本环境允许你导入和保存以前创建的 Python 脚本。 脚本环境中的脚本将保存到你选择的外部位置。 此外,当脚本准备就绪时,你可以通过单击“运行脚本”按钮来运行它

在这里插入图片描述

在 Blender 中保存、打开并运行脚本

最后,一旦脚本准备好运行,请转到“窗口/切换系统控制台”以访问显示代码输出的控制台。 下图可以更好地体现这一点。

在这里插入图片描述

切换系统控制台输出

4、Blender 控制台简介

Blender 控制台允许用户使用 Pyhton 语言将算法输入到 Blender 环境中。 当自动生成数据时,它非常有用。 在这里,我们解释了用于生成数据的不同步骤。

4.1 访问场景信息

在下面的代码中,你可以看到如何访问场景信息。 bpy.data.scenes 是在 Blender 中打开的所有场景的集合。 在我们的例子中,只创建了一个场景。 你可以使用命令 bpy.data.scenes[0] 或 bpy.data.scenes[‘Scene’] 访问它。

>>> bpy.data
<bpy_struct, BlendData at 0x000001C251997458>>>> bpy.data.scenes
<bpy_collection[1], BlendDataScenes>>>> bpy.data.scenes[0]
bpy.data.scenes['Scene']>>> bpy.data.scenes[1]
Traceback (most recent call last):File "<blender_console>", line 1, in <module>
IndexError: bpy_prop_collection[index]: index 1 out of range, size 1>>> scene = bpy.data.scenes[0]
>>> scene
bpy.data.scenes['Scene']

4.2 访问对象信息

我们还需要访问对象信息。 事实上,我们将修改几个对象参数,例如灯光的亮度和相机的位置。 bpy.data.objects 是场景中所有对象的集合。 在我们的例子中,我们有 15 个不同的对象。 因此,你可以通过两种不同的方式调用对象:

  • 使用语法 bpy.data.objects[x],其中 x 是 0 到 15 之间的数字,表示场景中对象的数量
  • 或者使用语法 _bpy.data.objects[‘Name’],其中 Name 是你要调用的对象的名称。
    然后,你可以将刚刚调用的对象存储在变量中以供进一步使用。
>>> bpy.data.objects
<bpy_collection[15], BlendDataObjects>>>> bpy.data.objects[0]
bpy.data.objects['Camera']>>> camera = bpy.data.objects['Camera']
>>> bpy.data.objects[1]
bpy.data.objects['Carre bleu']>>> bpy.data.objects[2]
bpy.data.objects['Conteneur coupe']>>> bpy.data.objects[3]
bpy.data.objects['Empty']>>> axe = bpy.data.objects[3]
>>> axe
bpy.data.objects['Empty']>>> camera
bpy.data.objects['Camera']>>> carre_bleu = bpy.data.objects['Carre bleu']
>>> carre_bleu
bpy.data.objects['Carre bleu']>>> bpy.data.objects['Light']
bpy.data.objects['Light']>>> light1 = bpy.data.objects['Light']
>>> light2 = bpy.data.objects['Light2']
>>> light1
bpy.data.objects['Light']>>> light2
bpy.data.objects['Light2']

4.3 修改对象信息

对于每个对象,可以在场景编辑器中修改它们的信息(位置、位置等)。 在下图中,可以看到如何修改对象的旋转。 了解对象参数的实用性对于能够创建适当的算法非常重要。 你可以看到,当修改参数时,其值也会在控制台中更新。

在这里插入图片描述

修改对象的参数

我们现在可以使用以下代码行修改控制台中的不同参数:

>>> axe.rotation_euler = (0,0,0)
>>> camera.location = (0,0,0.5)
>>> light1.data.energy = 50
>>> light2.data.energy = 0

light1.data.energy 允许你修改灯光的亮度。 camera.location 修改相机的位置。 axe.rotation_euler 修改对象“斧头”的旋转。

在这里插入图片描述

运行前面几行代码后得到的场景

现在可以修改其中一个参数来查看场景如何变化。

>>> axe.rotation_euler = (0,0,50)

这里我们修改了对象“斧头”的方向。

在这里插入图片描述
修改参数后得到的场景

5、生成训练数据的主要过程

为了训练我们的算法,我们需要大量数据。 因此,我们将创建一种算法,通过在场景中移动相机来从不同角度拍摄物体的照片。 我们还将修改灯光的亮度,以获得更能代表现实的数据集。

在这里插入图片描述

Blender使用环境图

以下算法由三个循环组成,每个循环都会修改相机的一个角度(请参见上图)。 在循环内,我们还修改灯光的亮度。 然后,对于相机的每个位置,我们拍摄场景照片并创建一个包含对象信息的文本文件。

在这里插入图片描述

生成物体图片的过程

这里可以看到算法生成的图片:

在这里插入图片描述

借助该算法,我们将获得几张图像,其中包含匹配的文本文件,其中包含对象的位置及其边界框。 然后该数据集将用于训练深度学习算法。

在这里插入图片描述

算法的预期结果

6、渲染类初始定义

为了实现一个包含上述算法并能够访问场景和其中对象的信息的程序,我们决定创建一个名为 Render 的完整类。

该类在下面初始化。 我们首先导入所有相关的库。 bpy 是允许我们访问和修改 Blender 元素信息的库。 在 Render 类的初始化期间,我们定义了将要操作的主要对象,例如定义为 self.camera 的相机、定义为 self.axis 的轴、定义为 self.light1 和 self.light2 的光源 1 和 2 ,以及保存到 self.objects 变量中的所有对象。

最后,但最重要的是,我们定义变量 self.camera_z_limits 将相机从 0.3 米移动到 1 米,每次 0.1 米。 我们定义 self.beta_limits 将相机沿 x 轴从 80° 旋转到 -80°,每个 rot_step 角度。 我们定义 self.gamma_limits 来沿 x 轴从 0° 到 360° 旋转相机,每个 rot_step 角度。 此外,我们定义 self.images_filepath 和 self.labels_filepath 这将是保存我们的程序生成的图像和标签的文件路径。

## Import all relevant libraries
import bpy
import numpy as np
import math as m
import random## Main Class
class Render:def __init__(self):## Scene information# Define the scene informationself.scene = bpy.data.scenes['Scene']# Define the information relevant to the <bpy.data.objects>self.camera = bpy.data.objects['Camera']self.axis = bpy.data.objects['Main Axis']self.light_1 = bpy.data.objects['Light1']self.light_2 = bpy.data.objects['Light2']self.obj_names = ['Rose Flower', 'Blue Square', 'Green star', 'Yellow hexagon', 'Orange losange','Rose oval', 'Blue rectangle', 'Green circle', 'Orange triangle']self.objects = self.create_objects() # Create list of bpy.data.objects from bpy.data.objects[1] to bpy.data.objects[N]## Render informationself.camera_d_limits = [0.2, 0.8] # Define range of heights z in m that the camera is going to pan throughself.beta_limits = [80, -80] # Define range of beta angles that the camera is going to pan throughself.gamma_limits = [0, 360] # Define range of gamma angles that the camera is going to pan through## Output information# Input your own preferred location for the images and labelsself.images_filepath = 'C:/Users/Federico Arenas/Desktop/Webinar/Tutorial Blender/Blender/Data'self.labels_filepath = 'C:/Users/Federico Arenas/Desktop/Webinar/Tutorial Blender/Blender/Data/Labels'def set_camera(self):self.axis.rotation_euler = (0, 0, 0)self.axis.location = (0, 0, 0)self.camera.location = (0, 0, 3)

7、平移物体并拍照的主要算法

现在,我们将 main_rendering_loop() 函数添加到 Render 类中。 该函数是上一节中所示算法的 Python 实现。 通过访问 self.camera、 self.axis、 self.light1 和 self.light2 信息,我们将能够在物体周围移动相机、拍照并提取标签。

最初,通过使用 self.calculate_n_renders(rot_step) 函数,我们能够计算将创建多少个渲染和标签。 rot_step 参数指示程序要拍摄照片的角度。 rot_step 越小,创建的渲染和标签就越多。 然后我们打印渲染次数的信息,并询问是否开始渲染。 这很重要,因为它允许我们估计 rot_step 将为我们提供多少渲染,如果太多,我们增加 rot_step,如果太少,我们减少它。

如果用户点击“Y”,则算法开始创建数据。 必须注意的是,我们必须进行一些重构并调整最初定义的限制 self.camera_z_limits、 self.beta_limits 和 self.gamma_limits。 进行此重构是因为 for 循环既不能循环小数,也不能循环负数,因此我们被迫将小数乘以 10,并使用 10° 到 170°,而不是 80° 到 -80°。

    def main_rendering_loop(self, rot_step):'''This function represent the main algorithm explained in the Tutorial, it accepts therotation step as input, and outputs the images and the labels to the above specified locations.'''## Calculate the number of images and labels to generaten_renders = self.calculate_n_renders(rot_step) # Calculate number of imagesprint('Number of renders to create:', n_renders)accept_render = input('\nContinue?[Y/N]:  ') # Ask whether to procede with the data generationif accept_render == 'Y': # If the user inputs 'Y' then procede with the data generation# Create .txt file that record the progress of the data generationreport_file_path = self.labels_filepath + '/progress_report.txt'report = open(report_file_path, 'w')# Multiply the limits by 10 to adapt to the for loopdmin = int(self.camera_d_limits[0] * 10)dmax = int(self.camera_d_limits[1] * 10)# Define a counter to name each .png and .txt files that are outputtedrender_counter = 0# Define the step with which the pictures are going to be takenrotation_step = rot_step# Begin nested loopsfor d in range(dmin, dmax + 1, 2): # Loop to vary the height of the camera## Update the height of the cameraself.camera.location = (0, 0, d/10) # Divide the distance z by 10 to re-factor current height# Refactor the beta limits for them to be in a range from 0 to 360 to adapt the limits to the for loopmin_beta = (-1)*self.beta_limits[0] + 90max_beta = (-1)*self.beta_limits[1] + 90for beta in range(min_beta, max_beta + 1, rotation_step): # Loop to vary the angle betabeta_r = (-1)*beta + 90 # Re-factor the current betafor gamma in range(self.gamma_limits[0], self.gamma_limits[1] + 1, rotation_step): # Loop to vary the angle gammarender_counter += 1 # Update counter## Update the rotation of the axisaxis_rotation = (m.radians(beta_r), 0, m.radians(gamma)) self.axis.rotation_euler = axis_rotation # Assign rotation to <bpy.data.objects['Empty']> object# Display demo information - Location of the cameraprint("On render:", render_counter)print("--> Location of the camera:")print("     d:", d/10, "m")print("     Beta:", str(beta_r)+" Deg")print("     Gamma:", str(gamma)+" Deg")## Configure lightingenergy1 = random.randint(0, 30) # Grab random light intensityself.light_1.data.energy = energy1 # Update the <bpy.data.objects['Light']> energy informationenergy2 = random.randint(4, 20) # Grab random light intensityself.light_2.data.energy = energy2 # Update the <bpy.data.objects['Light2']> energy information## Generate renderself.render_blender(render_counter) # Take photo of current scene and ouput the render_counter.png file# Display demo information - Photo informationprint("--> Picture information:")print("     Resolution:", (self.xpix*self.percentage, self.ypix*self.percentage))print("     Rendering samples:", self.samples)## Output Labelstext_file_name = self.labels_filepath + '/' + str(render_counter) + '.txt' # Create label file nametext_file = open(text_file_name, 'w+') # Open .txt file of the label# Get formatted coordinates of the bounding boxes of all the objects in the scene# Display demo information - Label constructionprint("---> Label Construction")text_coordinates = self.get_all_coordinates()splitted_coordinates = text_coordinates.split('\n')[:-1] # Delete last '\n' in coordinatestext_file.write('\n'.join(splitted_coordinates)) # Write the coordinates to the text file and output the render_counter.txt filetext_file.close() # Close the .txt file corresponding to the label## Show progress on batch of rendersprint('Progress =', str(render_counter) + '/' + str(n_renders))report.write('Progress: ' + str(render_counter) + ' Rotation: ' + str(axis_rotation) + ' z_d: ' + str(d / 10) + '\n')report.close() # Close the .txt file corresponding to the reportelse: # If the user inputs anything else, then abort the data generationprint('Aborted rendering operation')pass

你可能已经注意到,我们调用了之前创建的两个函数: self.render_blender(render_counter) 和 self.get_all_coordinates(resx, resy)。 第一个函数包含在源代码中,它改变图像大小和清晰度,拍摄一张照片并定义 _self.xpix、 self.ypix 和 self.percentage 变量,这些变量是拍摄的照片的大小及其提供的比例以 % 表示。 此函数最终将 render_counter.png 文件导出到 self.images_filepath 位置。

我们可以看到,当输入到 self.get_all_coordinates(resx, resy) 函数时, resx 和 resy 会考虑这些变量。 进行以下计算以提供图像的最终尺寸:

resx = final x size of the image = self.xpix * self.percentage * 0.01 # Multiply by 0.01 to divide by 100 and scale the final size
resy = final y size of the image = self.ypix * self.percentage * 0.01 # Multiply by 0.01 to divide by 100 and scale the final size

第二个功能将在下一节中进一步解释。 坐标恢复后,它们将被添加到创建的 render_counter.txt 文件中,该文件保存在 self.labels_filepath 位置。

8、从所有对象中提取标签

函数 get_all_coordinates(resx, resy) 循环遍历 self.objects 中的所有对象,并尝试使用函数 self.find_bounding_box(obj) 获取每个对象的坐标,该函数获取当前对象 obj,如果在相机视图中,则输出其坐标 。 这个函数在这里进一步解释,并集成到我们在这里编写的完整代码中。

接下来,如果找到该对象,则函数 self.format_coordinates(b_box, i, resx, resy) 从此格式重新格式化每个对象的标签:

Name_of_class_0 <top_x> <top_y> <bottom_x> <bottom_y>
Name_of_class_1 <top_x> <top_y> <bottom_x> <bottom_y>
Name_of_class_2 <top_x> <top_y> <bottom_x> <bottom_y>
...
...
Name_of_class_N <top_x> <top_y> <bottom_x> <bottom_y>

在这里插入图片描述

标签的初始格式

YOLO 使用的格式:

0 <center_x> <center_y> <bounding_box_width> <bounding_box_height>
1 <center_x> <center_y> <bounding_box_width> <bounding_box_height>
2 <center_x> <center_y> <bounding_box_width> <bounding_box_height>
...
...
N_classes <center_x> <center_y> <bounding_box_width> <bounding_box_height>

在这里插入图片描述

格式化后标签的格式

该函数可以在这里看到:

    def get_all_coordinates(self):'''This function takes no input and outputs the complete string with the coordinatesof all the objects in view in the current image'''main_text_coordinates = '' # Initialize the variable where we'll store the coordinatesfor i, objct in enumerate(self.objects): # Loop through all of the objectsprint("     On object:", objct)b_box = self.find_bounding_box(objct) # Get current object's coordinatesif b_box: # If find_bounding_box() doesn't return Noneprint("         Initial coordinates:", b_box)text_coordinates = self.format_coordinates(b_box, i) # Reformat coordinates to YOLOv3 formatprint("         YOLO-friendly coordinates:", text_coordinates)main_text_coordinates = main_text_coordinates + text_coordinates # Update main_text_coordinates variables whith each# line corresponding to each class in the frame of the current imageelse:print("         Object not visible")passreturn main_text_coordinates # Return all coordinates

最后, main_text_coordinates以字符串形式返回。

9、数据生成获得的结果

运行以下代码,该代码调用 Render() 类,初始化相机端,然后启动数据生成循环。

## Run data generation
if __name__ == '__main__':# Initialize rendering class as rr = Render()# Initialize camerar.set_camera()# Begin data generationrotation_step = 5r.main_rendering_loop(rotation_step)

该程序会将进度输出到 Blender控制台窗口。 它将把图像和标签输出到指定位置。
在这里插入图片描述

控制台窗口中的消息

如前所述,标签将具有以下格式。

0 <center_x> <center_y> <bounding_box_width> <bounding_box_height>
1 <center_x> <center_y> <bounding_box_width> <bounding_box_height>
2 <center_x> <center_y> <bounding_box_width> <bounding_box_height>
...
...
N_classes <center_x> <center_y> <bounding_box_width> <bounding_box_height>

最后,我们运行一个程序,将输出的标签绘制到输出的图像上,以验证这些标签是否正确指向每个对象。

在这里插入图片描述

10、使用 YOLO 和 Google Colab 进行测试

我们决定使用YOLOv3的Darknet架构,并在Google Colab中实现。 我们从 AlexeyAB 的 Darknet 存储库下载了 darknet 架构。

本文重点介绍 YOLO 应用程序自动生成训练数据的过程,而不是训练本身,因此本节的目的是展示我们结果的简短摘要。

为了获得最佳性能,我们进行了 5 次不同的测试。 然而,受到自己计算机处理能力的极大限制,使用Nvidia GeForce MX150 GPU,渲染大约10000张图像最多需要一天半的时间。 这意味着创建大约 100000 张图像的数据集是不可行的,因为我们还需要将计算机用于其他目的。

无论如何,训练输出了以下损失图。
在这里插入图片描述

我们对结果进行了总结,发现如果不能表示测试中的变化,那么增加数据量并不总是有帮助。 这可以从测试 1 和测试 5 的比较中看出。

在这里插入图片描述

考虑到我们使用低容量计算机花了大约一周的时间来获取数据并训练我们的模型,识别结果是令人满意的。 这意味着合成数据生成的可扩展性非常好。 有了更好的资源,我们就可以用更多的数据来训练我们的算法,甚至更加逼真。

在这里插入图片描述

下面可以看到处理合成测试数据的算法的演示,完整的视频可以在此处 查看。
在这里插入图片描述

下面可以看到针对真实测试数据的算法演示,完整视频可以在此处 查看。
在这里插入图片描述


原文链接:基于合成数据的YOLO训练 — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/150805.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】线程安全的集合类

文章目录 前言多线程环境使用 ArrayList多线程环境使用队列多线程环境使用哈希表1. HashTable2. ConcurrentHashMap 前言 前面我们学习了很多的Java集合类&#xff0c;像什么ArrayList、Queue、HashTable、HashMap等等一些常用的集合类&#xff0c;之前使用这些都是在单线程中…

MyBatis(JavaEE进阶系列4)

目录 前言&#xff1a; 1.MyBatis是什么 2.为什么要学习MyBatis框架 3.MyBatis框架的搭建 3.1添加MyBatis框架 3.2设置MyBatis配置 4.根据MyBatis写法完成数据库的操作 5.MyBatis里面的增删改查操作 5.1插入语句 5.2修改语句 5.3delete语句 5.4查询语句 5.5like查…

IDEA 生成 javadoc

IDEA 生成 javadoc 在IDEA工具栏tools中&#xff0c;打开选项Generate JavaDoc(生成javaDoc 文件) 配置参数

什么是事件对象(event object)?如何使用它获取事件信息?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【FISCO-BCOS】十六、多群组部署

目录 一、星形拓扑和并行多组 二、多群组部署&#xff08;星形拓扑&#xff09; 1、ipconf文件的编写 2、指定文件部署 3、检查节点共识 一、星形拓扑和并行多组 这是区块链应用中使用较广泛的两种组网方式 星形拓扑&#xff1a;中心机构节点同时属于多个群组&#xff0c;…

一、Excel VBA 是个啥?

Excel VBA 从入门到出门一、Excel VBA 是个啥&#xff1f;二、Excel VBA 简单使用 &#x1f44b;Excel VBA 是个啥&#xff1f; ⚽️1. Excel 中的 VBA 是什么&#xff1f;⚽️2. 为什么 VBA 很重要&#xff1f;⚽️3. 是否有无代码方法可以在 Excel 中实现工作流程自动化&…

深挖 Python 元组 pt.1

哈喽大家好&#xff0c;我是咸鱼 好久不见甚是想念&#xff0c;2023 年最后一次法定节假日已经结束了&#xff0c;不知道各位小伙伴是不是跟咸鱼一样今天就开始“搬砖”了呢&#xff1f; 我们知道元组&#xff08;tuple&#xff09;是 Python 的内置数据类型&#xff0c;tupl…

学信息系统项目管理师第4版系列20_风险管理

1. 针对不确定性的应对方法 1.1. 【高23上选58】 1.2. 收集信息 1.2.1. 可以对信息收集和分析工作进行规划&#xff0c;以便发现更多信息&#xff08;如进行研究、争取专家参与或进行市场分析&#xff09;来减少不确定性 1.3. 为多种结果做好准备 1.3.1. 制定可用的解决方…

手机投屏电脑软件AirServer5.6.3.0最新免费版本下载

随着智能手机的普及&#xff0c;越来越多的人喜欢用手机观看视频、玩游戏、办公等。但是&#xff0c;有时候手机屏幕太小&#xff0c;不够清晰&#xff0c;也不方便操作。这时候&#xff0c;如果能把手机屏幕投射到电脑上&#xff0c;就可以享受更大的视野&#xff0c;更流畅的…

nsoftware Cloud SMS 2022 .NET 22.0.8 Crack

nsoftware Cloud SMS 能够通过各种流行的消息服务&#xff08;包括 Twilio、Sinch、SMSGlobal、SMS.to、Vonage、Clickatell 等&#xff09;发送、接收和安排 SMS 消息&#xff0c;从而提供了一种简化且高效的消息服务方法。 Cloud SMS 提供单个 SMS 组件&#xff0c;允许通过…

微服务技术栈-Gateway服务网关

文章目录 前言一、为什么需要网关二、Spring Cloud Gateway三、断言工厂和过滤器1.断言工厂2.过滤器3.全局过滤器4.过滤器执行顺序 四、跨域问题总结 前言 在之前的文章中我们已经介绍了微服务技术中eureka、nacos、ribbon、Feign这几个组件&#xff0c;接下来将介绍另外一个组…

合成数据在计算机视觉任务中的应用指南

今年早些时候&#xff0c;我与 Cognizant 深度学习协会团队的一位经理进行了交谈。 他的团队使用深度学习算法创建概念验证&#xff08;展示商业机会的试点项目&#xff09;。 他注意到他的团队面临的主要挑战之一是获取此类 POC 的数据。 获取特定于某个问题的具有良好代表性的…

雷达波束高度估计、折射分类、大气波导现象概念

一、雷达波束高度估计 雷达波束在地球大气层中的传播并非直线,而是受到大气层的影响呈现出一种弯曲的形态,这种现象称为大气折射。这是由于地球大气的密度并非均匀,从地面到高空,大气的密度逐渐减小,因此电磁波在穿过大气层时,会因大气密度的变化而改变传播方向,形成弯曲…

2000至2022年中国月度植被覆盖度产品

简介&#xff1a; 中国区域2000至2022年月度植被覆盖度产品的空间分辨率250米&#xff0c;合成方式采用月最大值合成。本产品采用基于归一化植被指数&#xff08;NDVI&#xff09;像元二分模型&#xff0c;根据土地利用类型确定纯植被像元值和纯裸土像元值&#xff0c;计算中去…

cereal:支持C++11的开源序列化库

cereal:支持C11的开源序列化库 文章目录 一&#xff1a;引言二、cereal简介三、cereal的下载和使用 一&#xff1a;引言 序列化 (Serialization) 程序员在编写应用程序的时候往往需要将程序的某些数据存储在内存中&#xff0c;然后将其写入某个文件或是将它传输到网络中的另…

互联网图片安全风控实战训练营开营!

内容安全风控&#xff0c;即针对互联网产生的海量内容的外部、内部风险做宏观到微观的引导和审核&#xff0c;从内容安全领域帮助企业化解监管风险和社会舆论风险&#xff0c;其核心是识别文本、图片、视频、音频中的有害内容。 由于互联网内容类型繁杂、多如牛毛&#xff0c;加…

JVM技术文档--JVM诊断调优工具Arthas--阿里巴巴开源工具--一文搞懂Arthas--快速上手--国庆开卷!!

​ Arthas首页 简介 | arthas Arthas官网文档 Arthas首页、文档和下载 - 开源 Java 诊断工具 - OSCHINA - 中文开源技术交流社区 阿丹&#xff1a; 之前聊过了一些关于JMV中的分区等等&#xff0c;但是有同学还是在后台问我&#xff0c;还有私信问我&#xff0c;学了这些…

三维模型3DTile格式轻量化的纹理压缩和质量关系分析

三维模型3DTile格式轻量化的纹理压缩和质量关系分析 在三维模型的3DTile格式轻量化处理中&#xff0c;纹理压缩是一个重要环节。但是&#xff0c;纹理压缩和模型质量之间存在明显的关系需要权衡。以下是纹理压缩和模型质量关系的详细分析&#xff1a; 1、压缩率与纹理质量&…

【UE】在游戏运行时,通过选择uasset来生成静态网格体

目录 主要流程 步骤 一、创建用于包含静态网格体的Actor蓝图 二、按钮点击事件 效果 主要流程 用户点击按钮后产生一个文件对话框&#xff0c;用户通过文件对话框选择指定的文件夹&#xff0c;我们获取到这个文件夹路径后处理成“按路径获取资产”节点所需的输入&#x…

【开发篇】二十、SpringBoot整合RocketMQ

文章目录 1、整合2、消息的生产3、消费4、发送异步消息5、补充&#xff1a;安装RocketMQ 1、整合 首先导入起步依赖&#xff0c;RocketMQ的starter不是Spring维护的&#xff0c;这一点从starter的命名可以看出来&#xff08;不是spring-boot-starter-xxx&#xff0c;而是xxx-s…