数据集渲染任务管理——后端搭建
- 引言
- 一、主要功能
- 二、实现步骤
- 2.1 创建App
- (1)使用命令行创建App
- (2)注册App
- (3)配置路由
- 2.2 创建Model
- (1)配置媒体文件地址
- (2)创建model
- (3)迁移数据库
- 2.3 在Admin中注册模型
- 2.4 效果
引言
计算机专业硕士在读,主要研究方向是特定目标大斜视角目标检测与定位。因为要做的是特定目标,公开数据集较少,经过多方考虑还是决定要自建数据集。最终考虑的解决方案还是Blender+Python API的方式,项目起名叫RealEarthStudio。
这系列文章主要对开发过程进行记录,方便我个人后续查看,也给相类似方向的同学提供一个思路。
【项目目录】:项目目录链接
一、主要功能
功能:数据集渲染任务管理——后端搭建。
背景:现在开始开发第二个应用,主要实现数据集渲染任务管理。
效果:
码云项目链接:https://gitee.com/charlsewyq/RealEarthStudio
二、实现步骤
2.1 创建App
(1)使用命令行创建App
python manage.py startapp app2_rendering_task(2)注册App
- 在App目录下编辑
apps文件:app2_rendering_task/apps.py
fromdjango.appsimportAppConfigclassApp2RenderingTaskConfig(AppConfig):default_auto_field="django.db.models.BigAutoField"name="app2_rendering_task"verbose_name="🏷️ 应用2:数据集渲染任务管理模块"- 在主项目目录的
settings.py中添加app:
INSTALLED_APPS=[...,# 我的应用'app2_rendering_task.apps.App2RenderingTaskConfig',](3)配置路由
- 在App目录下创建
urls.py:
fromdjango.urlsimportpathfrom.importviews app_name='app2_rendering_task'urlpatterns=[# 子路由]- 在主项目目录的
urls.py修改:
fromdjango.contribimportadminfromdjango.urlsimportpath,include urlpatterns=[...# app2: 数据集渲染任务管理模块path("api/app2/",include("app2_rendering_task.urls")),]2.2 创建Model
我想创建渲染任务Model,包含以下字段:
- 渲染ID:字符串类型,模型的唯一标识,自动生成
- 渲染时间:时间类型,上传模型的时间,根据后台时间自动记录
- 目标模型:多对多,对应目标模型Model
app1_model_management.TargetModel - 场景模型:多对多,对应场景模型Model
app1_model_management.SceneModel - 日光方位角:浮点类型,阳光照射的方位角(限制为0-360°,默认为0°)
- 日光高低角:浮点类型,阳光照射的高低角(限制为0-90°,默认为90°)
- 相机距离:JSON类型,相机距离目标模型的距离数组(限制为正值)
- 相机高低角:JSON类型,相机照射的高低角数组(限制为0-90°)
- 相机方位角间隔:浮点类型,阳光照射的方位角(限制为0-360°,默认为45°)
- 渲染图像分辨率(宽):整型,渲染图像的宽。
- 渲染图像分辨率(高):整型,渲染图像的宽。
- 渲染图像像素:计算属性,渲染图像的像素值。
- 模型器类别:字符串类型,渲染器类别(EEVEE或CYCLES)
- 渲染图像地址:文件地址类型,只读,用于存储渲染结果,删除数据时自动删除对应文件夹中的文件
(1)配置媒体文件地址
- 在主项目目录创建文件夹:
media - 在
settings.py中添加以下内容:
MEDIA_URL='/media/'MEDIA_ROOT=os.path.join(BASE_DIR,'media')- 配置媒体文件服务:
在主项目目录下编辑urls.py文件:
...# 只在DEBUG模式下提供媒体文件服务ifsettings.DEBUG:urlpatterns+=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)(2)创建model
还需要完成以下功能:
- 自动生成UUID,确保模型ID不重复
- 自动记录时间
- 把目标模型和
- 删除数据时同步删除模型文件
- 修改文件数据时替换模型文件
在App目录下编辑model文件:app2_rendering_task/models.py
importosimportuuidfromdjango.dbimportmodelsfromdjango.utilsimporttimezonefromdjango.core.exceptionsimportValidationErrorimportshutilfromapp1_model_management.modelsimportTargetModel,SceneModel# ====== 验证器 ======defvalidate_azimuth(value):ifnot(0<=value<=360):raiseValidationError("方位角必须在 0° 到 360° 之间。")defvalidate_elevation(value):ifnot(0<=value<=90):raiseValidationError("高低角必须在 0° 到 90° 之间。")defvalidate_positive_number_list(value):"""验证是否为正数列表"""ifnotisinstance(value,list):raiseValidationError("必须是一个列表。")foriteminvalue:ifnotisinstance(item,(int,float))oritem<=0:raiseValidationError("所有值必须是正数。")defvalidate_elevation_list(value):"""验证高低角列表 0-90"""ifnotisinstance(value,list):raiseValidationError("必须是一个列表。")foriteminvalue:ifnotisinstance(item,(int,float))ornot(0<=item<=90):raiseValidationError("所有高低角必须在 0° 到 90° 之间。")# ====== 模型 ======defrendered_result_path(instance,filename):"""场景模型上传路径"""returnos.path.join("Render",f"{instance.render_time}-{instance.render_id}")classRenderingTask(models.Model):RENDERER_CHOICES=[('EEVEE','EEVEE'),('CYCLES','Cycles'),]# 任务信息render_id=models.UUIDField("渲染ID",default=uuid.uuid4,editable=False,unique=True,help_text="渲染任务的唯一标识")render_time=models.DateTimeField(verbose_name="渲染时间",default=timezone.now)# 模型target_models=models.ManyToManyField(TargetModel,verbose_name="目标模型",blank=True,related_name="rendering_tasks")scene_models=models.ManyToManyField(SceneModel,verbose_name="场景模型",blank=True,related_name="rendering_tasks")# 日光参数sun_azimuth=models.FloatField("日光方位角",default=0.0,validators=[validate_azimuth],help_text="阳光照射的方位角(0°-360°)")sun_elevation=models.FloatField("日光高低角",default=90.0,validators=[validate_elevation],help_text="阳光照射的高低角(0°-90°)")# 相机参数camera_distances=models.JSONField("相机距离",default=list,blank=True,validators=[validate_positive_number_list],help_text="相机到目标的距离列表(正值)")camera_elevations=models.JSONField("相机高低角",default=list,blank=True,validators=[validate_elevation_list],help_text="相机高低角列表(0°-90°)")camera_rotation_step=models.FloatField("相机方位角间隔",default=45.0,validators=[validate_azimuth],help_text="相机方位角采样间隔(0°-360°)")# 渲染分辨率image_width=models.PositiveIntegerField("渲染图像分辨率(宽)",default=1920)image_height=models.PositiveIntegerField("渲染图像分辨率(高)",default=1080)# 渲染器类别renderer_type=models.CharField("渲染器类别",max_length=10,choices=RENDERER_CHOICES,default='EEVEE')# 渲染结果文件rendered_result=models.FileField("渲染图像地址",upload_to=rendered_result_path,blank=True,null=True,help_text="系统自动生成的渲染结果图像(只读)")classMeta:verbose_name="01-渲染任务"verbose_name_plural="01-渲染任务"ordering=['-render_time']def__str__(self):returnf"{self.render_id}"@propertydefimage_pixels(self):"""计算属性:总像素数"""returnself.image_width*self.image_heightdefsave(self,*args,**kwargs):"""可选:在保存前调用 full_clean() 进行验证"""self.full_clean()super().save(*args,**kwargs)defdelete(self,*args,**kwargs):""" 重写 delete 方法: 1. 先删除关联的渲染输出文件夹 2. 再调用父类 delete 删除数据库记录 """ifself.rendered_result:# 获取文件的绝对路径folder_dir=self.rendered_result.path# 如果文件存在则删除ifos.path.isdir(folder_dir):shutil.rmtree(folder_dir)# 调用父类的delete方法删除数据库记录super().delete(*args,**kwargs)(3)迁移数据库
# 生成迁移文件python manage.py makemigrations app2_rendering_task# 应用迁移python manage.py migrate app2_rendering_task2.3 在Admin中注册模型
在App目录下编辑admin文件:app2_rendering_task/admin.py
fromdjango.contribimportadminfrom.modelsimport*fromdjango.utils.safestringimportmark_safe@admin.register(RenderingTask)classRenderingTaskAdmin(admin.ModelAdmin):list_display=['render_id','render_time','renderer_type','image_width','image_height','render_progress_display']search_fields=['render_id']list_filter=['renderer_type','render_time']readonly_fields=['render_id','render_time','render_progress','rendered_result']# 字段分组显示fieldsets=(('任务信息',{'fields':('render_id','render_time','renderer_type','render_progress')}),('模型配置',{'fields':('target_models','scene_models')}),('光照参数',{'fields':('sun_azimuth','sun_elevation')}),('相机参数',{'fields':('camera_distances','camera_elevations','camera_rotation_step')}),('图像设置',{'fields':('image_width','image_height')}),('渲染结果',{'fields':('rendered_result',)}))@admin.display(description="渲染状态")defrender_progress_display(self,obj):ifobj.render_progress==0:# 如果状态是pending,显示一个链接returnmark_safe(f'<a href="#">开始渲染</a>')else:# 否则显示当前状态returnf"{obj.render_progress}%"