Blender模拟结构光3D Scanner(三)获取相机观测点云的真值
模拟结构光3D Scanner时常见的一个问题是如何获取重建点云的真值在Blender中可以使用光线求交的方法从相机光心沿各像素发出射线与场景物体求交并将交点导出为点云文件。本文介绍了一个Blender插件用于通过光线追踪技术将相机视角下的3D场景转换为点云数据。该插件从相机光心向每个像素发射射线与场景物体求交后将交点坐标导出为ASC或NPZ格式的点云文件。核心功能包括1支持设置分辨率、采样步长等参数2可选择场景中的任意相机3提供点云数据可视化和导出功能。代码实现了射线投射计算、数据存储和用户界面交互适用于3D扫描、计算机视觉等需要将3D场景转换为点云数据的应用场景。插件安装后可在Blender的3D视图侧边栏中调用。以下是Blender插件代码在Preference-Add-ons中从文件导入导入方法参见Blender模拟结构光3D Scanner二投影仪内参数匹配-CSDN博客word_coordinate_mapper.pybl_info { name: 世界坐标映射工具, author: Your Name, version: (1, 1), blender: (2, 80, 0), location: View3D Sidebar 工具, description: 获取指定相机渲染图像每个像素对应的世界坐标, category: 3D View, } import bpy import numpy as np from mathutils import Vector # 属性组用于存储场景数据 class WorldCoordData(bpy.types.PropertyGroup): resolution: bpy.props.IntVectorProperty( name分辨率, size2, default(640, 480) ) hit_count: bpy.props.IntProperty( name命中点数, default0 ) step_size: bpy.props.IntProperty( name采样步长, default1 ) selected_camera: bpy.props.StringProperty( name选择相机, default ) class WORLD_COORD_OT_calculate(bpy.types.Operator): 计算指定相机视图的世界坐标映射 bl_idname world_coord.calculate bl_label 计算世界坐标 bl_options {REGISTER, UNDO} resolution_x: bpy.props.IntProperty( nameX分辨率, description输出图像的X分辨率, default640, min64, max4096 ) resolution_y: bpy.props.IntProperty( nameY分辨率, description输出图像的Y分辨率, default480, min64, max4096 ) step_size: bpy.props.IntProperty( name采样步长, description像素采样步长1每个像素2每2个像素以此类推, default2, min1, max10 ) save_to_file: bpy.props.BoolProperty( name保存到文件, description将结果保存到NPZ文件, defaultTrue ) camera_name: bpy.props.StringProperty( name相机, description选择要使用的相机, default ) def execute(self, context): 执行操作的主要函数 try: # 检查是否选择了相机 if not self.camera_name: self.report({ERROR}, 请选择一个相机) return {CANCELLED} # 获取选择的相机对象 camera_obj bpy.data.objects.get(self.camera_name) if not camera_obj or camera_obj.type ! CAMERA: self.report({ERROR}, 选择的相机无效或不存在) return {CANCELLED} # 执行射线投射计算 world_coords, hit_mask self.raycast_world_coordinates( camera_obj, self.resolution_x, self.resolution_y, self.step_size ) # 保存结果到场景属性 self.save_results_to_scene(context, camera_obj, world_coords, hit_mask) # 可选保存到文件 if self.save_to_file: self.save_to_asc(camera_obj, world_coords, hit_mask) self.report({INFO}, f计算完成相机 {camera_obj.name} 找到 {np.sum(hit_mask)} 个命中点) return {FINISHED} except Exception as e: self.report({ERROR}, f计算失败: {str(e)}) return {CANCELLED} def raycast_world_coordinates(self, camera_obj, res_x, res_y, step_size1): 通过射线投射获取世界坐标 scene bpy.context.scene depsgraph bpy.context.evaluated_depsgraph_get() # 初始化结果数组 world_coords np.full((res_y, res_x, 3), np.nan, dtypenp.float32) hit_mask np.zeros((res_y, res_x), dtypebool) # 获取相机矩阵 cam_matrix camera_obj.matrix_world cam_data camera_obj.data # 计算相机参数 aspect_ratio res_x / res_y sensor_width cam_data.sensor_width sensor_height sensor_width / aspect_ratio focal_length cam_data.lens # 进度更新 wm bpy.context.window_manager wm.progress_begin(0, res_y) try: # 遍历每个像素带步长 for y in range(0, res_y, step_size): if y % 10 0: # 每10行更新一次进度 wm.progress_update(y) # 新代码 if getattr(wm, is_modal, False) or getattr(wm, progress_abort, False): break for x in range(0, res_x, step_size): # 计算射线方向 ray_direction self.get_camera_ray_direction( camera_obj, x, y, res_x, res_y ) # 射线原点相机位置 ray_origin cam_matrix Vector((0, 0, 0)) # 执行射线投射 hit, location, normal, index, obj, matrix scene.ray_cast( depsgraph, ray_origin, ray_direction ) if hit: world_coords[y, x] np.array(location) hit_mask[y, x] True finally: wm.progress_end() return world_coords, hit_mask def get_camera_ray_direction(self, camera_obj, pixel_x, pixel_y, res_x, res_y): 计算相机射线方向 # 转换为标准化设备坐标 (-1 到 1) ndc_x (pixel_x / res_x) * 2.0 - 1.0 ndc_y 1.0 - (pixel_y / res_y) * 2.0 # Y轴翻转 # 考虑相机传感器和焦距 aspect_ratio res_x / res_y sensor_width camera_obj.data.sensor_width sensor_height sensor_width / aspect_ratio focal_length camera_obj.data.lens # 计算相机空间中的方向 if camera_obj.data.type PERSP: # 透视相机 direction Vector(( ndc_x * (sensor_width / 2) / focal_length, ndc_y * (sensor_height / 2) / focal_length, -1.0 # 相机看向-Z方向 )) else: # 正交相机 scale camera_obj.data.ortho_scale direction Vector(( ndc_x * scale / 2, ndc_y * scale / 2 / aspect_ratio, -1.0 )) # 转换到世界空间 direction_world camera_obj.matrix_world.to_3x3() direction direction_world.normalize() return direction_world def save_results_to_scene(self, context, camera_obj, world_coords, hit_mask): 保存结果到场景属性 scene context.scene # 更新场景属性 scene.world_coord_data.resolution (world_coords.shape[1], world_coords.shape[0]) scene.world_coord_data.hit_count int(np.sum(hit_mask)) scene.world_coord_data.step_size self.step_size scene.world_coord_data.selected_camera camera_obj.name # 保存原始数据可选如果需要后续访问 if not hasattr(scene, world_coord_raw_data): scene[world_coord_raw_data] {} scene[world_coord_raw_data] { camera_name: camera_obj.name, timestamp: bpy.context.scene.frame_current, resolution: (world_coords.shape[1], world_coords.shape[0]) } def save_to_asc(self, camera_obj, world_coords, hit_mask): 将命中点的世界坐标保存为 .asc 文件ASCII 点云 import os from datetime import datetime # 1) 输出目录 output_dir os.path.join(bpy.path.abspath(//), world_coords) os.makedirs(output_dir, exist_okTrue) # 2) 文件名 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) filename fworld_coords_{camera_obj.name}_{timestamp}.asc filepath os.path.join(output_dir, filename) # 3) 提取命中点坐标 y_idx, x_idx np.where(hit_mask) # 命中像素坐标 points world_coords[y_idx, x_idx] # Nx3 # 4) 写入 .asc try: with open(filepath, w) as f: # 可按需写表头CloudCompare 识别 f.write(f# .asc point cloud generated by Blender addon\n) f.write(f# camera: {camera_obj.name}\n) # 逐行写 xyz np.savetxt(f, points, fmt%.6f) self.report({INFO}, f已保存 asc: {filepath}) except Exception as e: self.report({ERROR}, f保存 asc 失败: {str(e)}) def save_to_npz(self, camera_obj, world_coords, hit_mask): 保存结果到NPZ文件 import os from datetime import datetime # 创建输出目录 output_dir os.path.join(bpy.path.abspath(//), world_coords) os.makedirs(output_dir, exist_okTrue) # 生成文件名 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) filename fworld_coords_{camera_obj.name}_{timestamp}.npz filepath os.path.join(output_dir, filename) # 保存数据 np.savez_compressed( filepath, world_coordsworld_coords, hit_maskhit_mask, camera_namecamera_obj.name, camera_matrixnp.array(camera_obj.matrix_world), resolutionnp.array([world_coords.shape[1], world_coords.shape[0]]), step_sizeself.step_size, timestamptimestamp ) self.report({INFO}, f数据已保存到: {filepath}) def invoke(self, context, event): 调用操作时显示属性对话框 # 设置默认相机如果场景有相机 if context.scene.camera and not self.camera_name: self.camera_name context.scene.camera.name return context.window_manager.invoke_props_dialog(self) def draw(self, context): 绘制操作属性对话框 layout self.layout # 相机选择下拉框 row layout.row() row.label(text选择相机:) row layout.row() row.prop(self, camera_name, text) # 分辨率设置 row layout.row() row.prop(self, resolution_x) row layout.row() row.prop(self, resolution_y) # 其他设置 row layout.row() row.prop(self, step_size) row layout.row() row.prop(self, save_to_file) # 显示当前选择的相机信息 camera_obj bpy.data.objects.get(self.camera_name) if camera_obj and camera_obj.type CAMERA: box layout.box() box.label(text相机信息:, iconCAMERA_DATA) box.label(textf名称: {camera_obj.name}) box.label(textf类型: {camera_obj.data.type}) box.label(textf焦距: {camera_obj.data.lens}mm) class WORLD_COORD_PT_panel(bpy.types.Panel): 创建UI面板 bl_label 世界坐标映射 bl_idname WORLD_COORD_PT_panel bl_space_type VIEW_3D bl_region_type UI bl_category 工具 def draw(self, context): layout self.layout scene context.scene # 相机选择 row layout.row() row.label(text选择相机:) row layout.row() row.prop_search(scene.world_coord_data, selected_camera, scene, objects, text, iconCAMERA_DATA) # 检查选择的相机是否有效 camera_obj None if scene.world_coord_data.selected_camera: camera_obj bpy.data.objects.get(scene.world_coord_data.selected_camera) if camera_obj and camera_obj.type CAMERA: # 显示相机信息 box layout.box() box.label(text相机信息:, iconINFO) box.label(textf名称: {camera_obj.name}) box.label(textf类型: {camera_obj.data.type}) box.label(textf焦距: {camera_obj.data.lens}mm) # 计算按钮 row layout.row() row.operator(world_coord.calculate, text计算世界坐标, iconCAMERA_DATA) else: # 警告信息 box layout.box() box.label(text请选择一个有效的相机, iconERROR) if scene.camera: box.label(textf场景相机: {scene.camera.name}) # 仍然显示计算按钮但会弹出设置对话框 row layout.row() op row.operator(world_coord.calculate, text计算世界坐标, iconCAMERA_DATA) # 显示上次计算结果 if hasattr(scene, world_coord_data) and scene.world_coord_data.hit_count 0: data scene.world_coord_data box layout.box() box.label(text上次计算结果:, iconTEXT) box.label(textf相机: {data.selected_camera}) box.label(textf分辨率: {data.resolution[0]} x {data.resolution[1]}) box.label(textf命中点数: {data.hit_count}) box.label(textf采样步长: {data.step_size}) # 可视化按钮 row layout.row() row.operator(world_coord.visualize, text可视化结果, iconHIDE_OFF) class WORLD_COORD_OT_visualize(bpy.types.Operator): 可视化世界坐标结果 bl_idname world_coord.visualize bl_label 可视化结果 bl_description 在3D视图中显示世界坐标点 def execute(self, context): scene context.scene # 检查是否有计算结果 if not hasattr(scene, world_coord_raw_data): self.report({WARNING}, 没有找到计算结果数据) return {CANCELLED} # 这里可以添加可视化代码 # 例如创建空物体表示坐标点或者绘制点云 self.report({INFO}, 开始可视化世界坐标点) # 简单的可视化示例在命中点位置创建空物体 try: # 清除之前的可视化对象 self.clear_visualization_objects(scene) # 这里可以添加具体的可视化代码 # 由于原始数据可能很大建议使用采样或简化表示 self.report({INFO}, 可视化完成) except Exception as e: self.report({ERROR}, f可视化失败: {str(e)}) return {FINISHED} def clear_visualization_objects(self, scene): 清除之前创建的可视化对象 # 删除名称以vis_开头的空物体 objects_to_remove [obj for obj in scene.objects if obj.name.startswith(vis_) and obj.type EMPTY] for obj in objects_to_remove: bpy.data.objects.remove(obj, do_unlinkTrue) # 场景中所有相机的列表属性用于UI def get_camera_objects(self, context): 获取场景中所有相机的列表 items [] cameras [obj for obj in context.scene.objects if obj.type CAMERA] for i, camera in enumerate(cameras): items.append((camera.name, camera.name, f相机: {camera.name}, CAMERA_DATA, i)) if not items: items.append((NONE, 无相机, 场景中没有相机, ERROR, 0)) return items # 注册和取消注册函数 def register(): bpy.utils.register_class(WorldCoordData) bpy.utils.register_class(WORLD_COORD_OT_calculate) bpy.utils.register_class(WORLD_COORD_PT_panel) bpy.utils.register_class(WORLD_COORD_OT_visualize) # 添加场景属性 bpy.types.Scene.world_coord_data bpy.props.PointerProperty(typeWorldCoordData) def unregister(): bpy.utils.unregister_class(WorldCoordData) bpy.utils.unregister_class(WORLD_COORD_OT_calculate) bpy.utils.unregister_class(WORLD_COORD_PT_panel) bpy.utils.unregister_class(WORLD_COORD_OT_visualize) # 清理场景属性 if hasattr(bpy.types.Scene, world_coord_data): del bpy.types.Scene.world_coord_data if __name__ __main__: register()

相关新闻

能源物联网系统由什么组成

能源物联网系统由什么组成

第一层:端(感知层)—— 人体的“神经末梢和五官”这是最底层的硬件设备,负责感知和执行,是数据的源头。感知类(测量):智能电表(不仅计费,还能记录电压/电流/功…

2026/8/11 18:36:43 阅读更多 →
瑞芯微RV1126B开发板(EASY-EAI-PI2) yolov8训练部署教程

瑞芯微RV1126B开发板(EASY-EAI-PI2) yolov8训练部署教程

Yolov8简介 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的基于YOLOV5进行更新的 下一个重大更新版本,目前支持图像分类、物体检测和实例分割任务,鉴于Yolov5的良好表现,Yolov8在还没有开源时就收到了用户的广泛关注。其主要结构如下…

2026/8/11 18:36:43 阅读更多 →
Cortex-M 与 RTOS:先拆中断、任务还是驱动边界

Cortex-M 与 RTOS:先拆中断、任务还是驱动边界

Cortex-M 与 RTOS:先拆中断、任务还是驱动边界 拆 RTOS 功能时,先从数据如何进入、在哪里处理、怎样输出开始,而不是先按“一个模块一个任务”分工。任务拆得太早,往往只会增加队列、锁和排障难度。 找到不可阻塞的路径 先标出中断…

2026/8/11 18:36:43 阅读更多 →

最新新闻

多模态文档解析模型dots.mocr:从OCR文本识别到结构化文档理解

多模态文档解析模型dots.mocr:从OCR文本识别到结构化文档理解

1. 项目概述:当OCR不再只是“识字”最近在文档智能和OCR圈子里,一个来自华中科技大学和小红书hi lab的开源项目dots.mocr引起了不小的震动。如果你还在为从扫描件或复杂PDF里提取结构化信息而头疼,或者对传统OCR只能识别文字、却把表格、公式…

2026/8/14 4:22:04 阅读更多 →
基于Apriori算法的Python商品推荐系统全栈开发实践

基于Apriori算法的Python商品推荐系统全栈开发实践

1. 先搞清楚这个项目到底要做什么,以及它适合谁这个“Python商品推荐系统”毕业设计,核心目标很明确:搭建一个能自动抓取商品数据、分析用户行为、并给出个性化推荐的全栈Web应用。它不是一个简单的算法演示,而是一个从数据采集、…

2026/8/14 4:22:04 阅读更多 →
嵌入式面试总结(十三)——Flash

嵌入式面试总结(十三)——Flash

一、前言本文旨在系统梳理嵌入式系统中 Flash 存储技术的核心知识点,为嵌入式软件工程师、驱动开发者以及正在准备相关技术面试的读者提供一份清晰的参考指南。内容涵盖 Flash 的基本类型、操作特性、文件系统、驱动模型以及常见的面试问题与实战要点,帮…

2026/8/14 4:22:04 阅读更多 →
软件开发设计文档:从认知转变到实践指南

软件开发设计文档:从认知转变到实践指南

1. 从“文档无用论”到“文档即资产”的认知转变 在软件开发这个行当里待久了,你会发现一个挺有意思的现象:一提写设计文档,不少一线开发者的第一反应是皱眉、叹气,甚至私下里嘀咕“又搞形式主义”。我自己也经历过这个阶段&#…

2026/8/14 4:22:04 阅读更多 →
还在手动解包PKG和DMG?Brigadier一条命令搞定Mac的Boot Camp驱动

还在手动解包PKG和DMG?Brigadier一条命令搞定Mac的Boot Camp驱动

还在手动解包PKG和DMG?Brigadier一条命令搞定Mac的Boot Camp驱动 【免费下载链接】brigadier Fetch and install Boot Camp ESDs with ease. 项目地址: https://gitcode.com/gh_mirrors/bri/brigadier 想象这样一个周六:你刚给 MacBook Pro 装好 …

2026/8/14 4:22:04 阅读更多 →
Hive SQL与Spark SQL核心差异解析:从执行引擎到实战选型

Hive SQL与Spark SQL核心差异解析:从执行引擎到实战选型

1. 项目概述:从一次数据查询的“卡顿”说起 几年前,我还在一个数据仓库团队里负责报表开发。有一天,业务方紧急需要一个跨年度的用户行为漏斗分析,数据量在百亿级别。我像往常一样,熟练地打开Hive客户端,编…

2026/8/14 4:21:04 阅读更多 →

日新闻

临沂网站建设铭镇:深耕本土数字生态,以匠心铸就企业品牌核心竞争力

临沂网站建设铭镇:深耕本土数字生态,以匠心铸就企业品牌核心竞争力

在这个流量为王、视觉至上的互联网时代,对于临沂乃至整个山东乃至全国的传统中小企业来说,拥有一张精美的“数字名片”早已不再是可选项,而是生存的必答题。每当夜幕降临,沂河两岸灯火辉煌,物流之都的喧嚣逐渐沉淀为对未来的思考。我们常常听到老板们在茶余饭后探讨:为什…

2026/8/14 0:00:26 阅读更多 →
Flutter与OpenHarmony实现剧本杀组队表单开发实战

Flutter与OpenHarmony实现剧本杀组队表单开发实战

1. 项目概述在移动应用开发领域,跨平台框架Flutter因其高效的开发体验和出色的性能表现,已经成为众多开发者的首选。而OpenHarmony作为新兴的操作系统平台,其开放性和灵活性为开发者提供了全新的可能性。本文将聚焦于一个实际应用场景——剧本…

2026/8/14 0:00:26 阅读更多 →
大连网站建设找简维科技:为您打造懂业务更懂用户的数字化转型引擎

大连网站建设找简维科技:为您打造懂业务更懂用户的数字化转型引擎

在这个数字化浪潮席卷全球的今天,企业想要在激烈的市场竞争中站稳脚跟,拥有一张好看的“数字名片”已经远远不够了。很多老板在刚开始接触互联网业务时,都有一个共同的困惑:为什么我花了钱建的网站,就像是在真空中自嗨?访客进来转了两圈就跑了,线索石沉大海,甚至连客服…

2026/8/14 0:01:27 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/13 2:38:34 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/13 10:41:52 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/13 10:41:51 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/13 10:41:50 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/13 10:41:49 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/13 10:41:49 阅读更多 →