news 2026/1/12 16:55:55

基于 ContextCapture SDK 的 Python 自动化三维建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 ContextCapture SDK 的 Python 自动化三维建模

基于 ContextCapture SDK 的 Python 自动化三维建模

ContextCapture 是 Bentley 提供的一款商业级三维重建软件,广泛应用于无人机倾斜摄影、实景三维建模等场景。

本文基于 ContextCapture 官方提供的 MasterKernel SDK,介绍如何通过 Python 脚本实现对 ContextCapture 建模流程的自动化控制,包括项目创建、空中三角测量(AT)、三维重建以及多种格式成果的顺序生产。

ContextCapture MasterKernel SDK介绍:https://docs.bentley.com/LiveContent/web/ContextCapture Help-v18/zh-cn/GUID-8A35B878-C2BB-4F01-8D5A-18321EF04410.html

1.安装ContextCapture

基于ContextCapture的安装网上有很多详细的教程,本文不再对安装步骤进行详细展开。
本文测试环境使用的ContextCapture版本为v10.18.0.232,后续示例均基于该版本,仅供参考。

2.配置适合ContextCapture的Python环境

在 ContextCapture 安装目录的sdk/dist文件夹中,可以找到官方提供的 Python SDK 安装包(.whl文件)。

例如:
ccmasterkernel-10.18.0.232-cp36-cp36m-win_amd64.whl

其中“cp36-cp36m”表示只支持 Python3.6 相关版本(要安装正确的Python版本)。

安装完成Python环境后,打开命令行进入sdk/dist文件夹下,执行pip install ccmasterkernel-10.18.0.232-cp36-cp36m-win_amd64.whl安装好这个包。

3.编写Python脚本

关于sdk使用的相关文档说明在sdk/doc/html文件夹下。

下面给出一个完整的 Python 脚本示例,用于演示如何通过 ContextCapture SDK 实现无人机影像的自动化三维建模流程。

该脚本后续可根据实际需求进行裁剪或扩展。

此脚本是一个用于自动化 ContextCapture (CC) 建模流程的 Python 脚本。它能够自动创建项目、提交空三运算 (AT),并按顺序生成多种格式的三维模型(OBJ, LAS, 3D Tiles)。

注意:正式运行脚本前,需确保ContextCapture Engine 已启动,否则任务无法正常提交;

3.1命令行参数

脚本支持通过命令行参数进行灵活配置:

参数必选/可选默认值说明
--photos必选-照片所在的源文件夹路径。支持 jpg, tif, png 等格式。
--project必选-项目输出的根文件夹路径。脚本将在此目录下创建.ccm项目文件和各个产物文件夹。
--memory可选16.0目标内存使用量 (GB)。用于控制分块大小,建议设置为机器物理内存的 50%-70%。
--formats可选全部指定要生产的格式列表。可选值:OBJ,LAS,3DTiles。多个值之间用空格分隔。

3.2 使用示例

在命令行 (CMD 或 PowerShell) 中运行以下命令。

1) 基础用法

生成所有默认格式 (OBJ, LAS, 3D Tiles),默认使用 16GB 内存限制。

/* by 01022.hk - online tools website : 01022.hk/zh/generateethwallets.html */ python test_cc_sequential.py --photos "D:\MyData\Mission_01\Images" --project "D:\Projects\Mission_01"
2)指定输出格式

如果你只需要点云 (LAS) 和 OBJ 模型,不需要 3D Tiles:

/* by 01022.hk - online tools website : 01022.hk/zh/generateethwallets.html */ python test_cc_sequential.py --photos "D:\Images" --project "D:\Output" --formats LAS OBJ
3)自定义内存限制

针对大内存 (例如 64GB 内存),可以调高限制以加快处理速度 (例如设为 48GB):

python test_cc_sequential.py --photos "D:\Images" --project "D:\Output" --memory 48.0

针对小内存机器 (例如 16GB 内存),建议调低限制 (例如 8GB):

python test_cc_sequential.py --photos "D:\Images" --project "D:\Output" --memory 8.0

3.3 输出结构

运行完成后,在指定的--project目录下会自动生成以下结构:

D:\Projects\Mission_01\ <-- 项目根目录 │ Mission_01.ccm <-- CC 工程文件 │ cc_sequential.log <-- 详细运行日志 │ ├─ Production_OBJ\ <-- OBJ 模型输出目录 │ metadata.xml │ ... │ ├─ Production_LAS\ <-- LAS 点云输出目录 │ cloud.las │ ... │ └─ Production_3DTiles\ <-- Cesium 3D Tiles 输出目录 tileset.json ...

3.4 日志

脚本运行过程中,简要信息会显示在屏幕上。
详细的带时间戳的日志会保存在脚本同级目录下的cc_sequential.log文件中。

3.5 具体代码实现

""" ContextCapture 顺序多格式生产脚本 支持 OBJ、LAS 点云、Cesium 3D Tiles 三种格式 使用分块处理 (Tiling) 控制内存 顺序执行避免内存不足 """ import sys import time import os import logging from logging.handlers import RotatingFileHandler import traceback import argparse # --- 日志配置 --- # 日志文件名为 cc_sequential.log,位于脚本所在目录 log_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cc_sequential.log') logger = logging.getLogger('CC_Production') logger.setLevel(logging.INFO) # 1. RotatingFileHandler: 10MB 切割,保留 5 个备份 # maxBytes = 10 * 1024 * 1024 = 10485760 B rf_handler = RotatingFileHandler(log_file_path, maxBytes=10 * 1024 * 1024, backupCount=5, encoding='utf-8') file_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') rf_handler.setFormatter(file_formatter) logger.addHandler(rf_handler) # 2. StreamHandler: 输出到控制台 console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(logging.Formatter('%(message)s')) # 控制台输出保持原样,纯文本方便阅读 logger.addHandler(console_handler) # ---------------- try: import ccmasterkernel logger.info("成功导入 ccmasterkernel 模块") except ImportError: logger.error("错误: 无法导入 ccmasterkernel 模块。") sys.exit(1) def parse_args(): parser = argparse.ArgumentParser(description="ContextCapture 顺序多格式生产脚本") # 路径参数 (必须) parser.add_argument('--photos', required=True, help='照片所在目录路径') parser.add_argument('--project', required=True, help='项目输出目录路径') # 可选参数 parser.add_argument('--memory', type=float, default=16.0, help='目标内存使用量 (GB),默认 16.0') parser.add_argument('--formats', nargs='+', default=['OBJ', 'LAS', '3DTiles'], choices=['OBJ', 'LAS', '3DTiles'], help='指定要生产的格式 (默认全部): OBJ, LAS, 3DTiles') return parser.parse_args() def main(): args = parse_args() # 定义路径变量 (从 args 获取) project_dir = args.project # 自动根据目录名生成项目文件名 project_name_base = os.path.basename(os.path.normpath(project_dir)) project_file_path = os.path.join(project_dir, f"{project_name_base}.ccm") photos_dir = args.photos # 多格式输出目录 production_obj_dir = os.path.join(project_dir, "Production_OBJ") production_las_dir = os.path.join(project_dir, "Production_LAS") production_3dtiles_dir = os.path.join(project_dir, "Production_3DTiles") logger.info("\n" + "=" * 60) logger.info("ContextCapture 顺序多格式自动化脚本 (CLI Mode)") logger.info("=" * 60) logger.info(f"项目文件路径: {project_file_path}") logger.info(f"照片目录: {photos_dir}") logger.info(f"目标内存: {args.memory} GB") # 确定要生产的格式 formats_to_produce_list = [] # 格式定义映射: Name -> (DriverName, OutputDir, Description) format_map = { 'OBJ': ("OBJ", production_obj_dir, "OBJ 网格模型"), 'LAS': ("LAS", production_las_dir, "LAS 点云"), '3DTiles': ("Cesium 3D Tiles", production_3dtiles_dir, "Cesium 3D Tiles") } req_formats = args.formats logger.info(f"计划生产格式: {', '.join(req_formats)}") # 构建 production list # 保持原有顺序: OBJ -> LAS -> 3DTiles (如果被选中) if 'OBJ' in req_formats: formats_to_produce_list.append(('OBJ', *format_map['OBJ'])) if 'LAS' in req_formats: formats_to_produce_list.append(('LAS', *format_map['LAS'])) if '3DTiles' in req_formats: formats_to_produce_list.append(('Cesium 3D Tiles', *format_map['3DTiles'])) logger.info("\n输出位置预览:") for _, _, out_dir, desc in formats_to_produce_list: logger.info(f" - {desc}: {out_dir}") logger.info("\n注意: 选中格式将依次执行,避免内存不足") logger.info("=" * 60 + "\n") # 确保项目目录存在 if not os.path.exists(project_dir): try: os.makedirs(project_dir) except OSError as e: logger.error(f"错误: 无法创建项目目录 {project_dir}: {e}") return # 1. 创建并配置项目 logger.info("正在创建项目...") project = ccmasterkernel.Project() project.setName(project_name_base) # 使用目录名作为项目名 project.setProjectFilePath(project_file_path) # 2. 创建区块并添加照片 logger.info("正在创建初始区块...") block = ccmasterkernel.Block(project) project.addBlock(block) block.setName("InitialBlock") logger.info("正在添加照片...") photogroups = block.getPhotogroups() if os.path.exists(photos_dir): photo_files = [f for f in os.listdir(photos_dir) if f.lower().endswith(('.jpg', '.jpeg', '.tif', '.tiff', '.png'))] if not photo_files: logger.warning(f"警告: 在 {photos_dir} 中未找到照片文件。") for f in photo_files: full_path = os.path.join(photos_dir, f) photogroups.addPhotoInAutoMode(full_path) logger.info(f"已尝试添加 {len(photo_files)} 张照片。") else: logger.error(f"错误: 照片目录 {photos_dir} 不存在。") return # 验证区块是否有效 if not block.isReadyForAT(): logger.error("错误: 区块未准备好进行 AT。") return # 保存项目 logger.info("正在保存项目...") save_error = project.writeToFile() if not save_error.isNone(): logger.error(f"保存项目失败: {save_error.message}") return logger.info("项目保存成功。") # 3. 空中三角测量 (AT) logger.info("\n准备空中三角测量 (AT) 区块...") block_at = ccmasterkernel.Block(project) project.addBlock(block_at) block_at.setName("AT_Block") block_at.setBlockTemplate(ccmasterkernel.BlockTemplate.Template_adjusted, block) # 保存更新后的项目 logger.info("正在保存更新后的项目...") save_error = project.writeToFile() if not save_error.isNone(): logger.error(f"保存项目失败: {save_error.message}") return logger.info("开始提交 AT 处理...") at = block_at.getAT() if at is None: logger.error("错误: 无法获取 AT 对象。") return # 提交 AT 处理 submit_error = at.submitProcessing() if not submit_error.isNone(): logger.error(f"AT 提交失败: {submit_error.message}") return # 监控 AT 任务 if not monitor_job(at, "AT"): logger.error("AT 处理未成功完成,脚本终止。") return logger.info("AT 处理完成。\n") # 4. 重建 (Reconstruction) - 配置分块处理 logger.info("=" * 60) logger.info("创建重建项目...") logger.info("=" * 60) reconstruction = ccmasterkernel.Reconstruction(block_at) block_at.addReconstruction(reconstruction) reconstruction.setName("Reconstruction_Sequential") # 配置重建设置 logger.info("\n配置重建设置...") settings = reconstruction.getSettings() reconstruction.setSettings(settings) # 配置分块处理 (Tiling) logger.info("配置分块处理 (Tiling)...") tiling = reconstruction.getTiling() tiling.tilingMode = ccmasterkernel.TilingMode.TilingMode_adaptive tiling.targetMemoryUse = args.memory # 使用命令行参数 tiling.overlapRatio = 0.2 tiling.discardEmptyTiles = True reconstruction.setTiling(tiling) logger.info(f" - 模式: Adaptive (自动分块)") logger.info(f" - 目标内存: {tiling.targetMemoryUse} GB") logger.info(f" - 瓦片重叠率: {tiling.overlapRatio * 100}%") # 保存重建配置 logger.info("\n保存重建配置...") save_error = project.writeToFile() if not save_error.isNone(): logger.error(f"保存项目失败: {save_error.message}") return # 获取瓦片数量 num_tiles = reconstruction.getNumInternalTiles() logger.info(f"\n重建包含 {num_tiles} 个瓦片") # 5. 顺序生产 - 依次执行每种格式 logger.info("\n" + "=" * 60) logger.info("开始顺序生产(依次执行,避免内存不足)") logger.info("=" * 60) results = [] # 遍历我们构建的格式列表 for idx, (prod_name, driver_name, output_dir, description) in enumerate(formats_to_produce_list, 1): logger.info("\n" + "=" * 60) logger.info(f"第 {idx}/{len(formats_to_produce_list)} 步: 生产 {description}") logger.info("=" * 60) # 创建生产 logger.info(f"\n配置 {description}...") production = ccmasterkernel.Production(reconstruction) reconstruction.addProduction(production) production.setName(f"Production_{prod_name}") production.setDriverName(driver_name) production.setDestination(output_dir) logger.info(f" ✓ {description} 配置完成") # 保存项目 save_error = project.writeToFile() if not save_error.isNone(): logger.error(f" ✗ 保存项目失败: {save_error.message}") results.append((description, False)) continue # 添加所有瓦片任务 logger.info(f"\n为 {description} 添加 {num_tiles} 个瓦片任务...") production_jobs = [] try: for i in range(num_tiles): tile = reconstruction.getInternalTile(i) job = ccmasterkernel.TileProductionJob(production, tile) production.addProductionJob(job) production_jobs.append(job) logger.info(f" ✓ 已添加 {num_tiles} 个瓦片任务") except Exception as e: logger.error(f" ✗ 添加任务时出错: {e}") results.append((description, False)) continue # 提交生产 logger.info(f"\n提交 {description} 生产...") submit_error = production.submitProcessing() if not submit_error.isNone(): logger.error(f" ✗ 提交失败: {submit_error.message}") results.append((description, False)) continue logger.info(f" ✓ 提交成功") # 监控所有瓦片任务 logger.info(f"\n监控 {description} 任务({num_tiles} 个瓦片)...") all_tiles_success = True for tile_idx, job in enumerate(production_jobs): job_desc = f"{description} - 瓦片 {tile_idx}" if not monitor_job(job, job_desc): logger.error(f" ✗ 瓦片 {tile_idx} 处理失败") all_tiles_success = False break # 如果一个瓦片失败,停止监控后续瓦片 else: logger.info(f" ✓ 瓦片 {tile_idx} 处理成功") if all_tiles_success: logger.info(f"\n✓ {description} 所有瓦片处理成功!") logger.info(f" 输出位置: {output_dir}") results.append((description, True)) else: logger.error(f"\n✗ {description} 处理失败") results.append((description, False)) # 最终结果汇总 logger.info("\n" + "=" * 60) logger.info("生产结果汇总") logger.info("=" * 60) for description, success in results: status = "✓ 成功" if success else "✗ 失败" logger.info(f" {status}: {description}") all_success = all(success for _, success in results) if all_success: logger.info("\n" + "=" * 60) logger.info("所有请求的格式生产成功完成!") logger.info("=" * 60) else: logger.error("\n部分格式生产失败,请检查日志获取详细信息。") def monitor_job(job_object, job_name): """ 通用任务监控函数 """ previous_status = ccmasterkernel.JobStatus.Job_unknown while True: # 更新状态 try: job_object.updateJobStatus() except AttributeError: pass status = job_object.getJobStatus() # 显示状态变化 if status != previous_status: status_str = ccmasterkernel.jobStatusAsString(status) logger.info(f" [{job_name}] {status_str}") previous_status = status # 检查是否结束 if status in [ccmasterkernel.JobStatus.Job_completed, ccmasterkernel.JobStatus.Job_failed, ccmasterkernel.JobStatus.Job_cancelled]: break time.sleep(2) # 最终结果检查 if status == ccmasterkernel.JobStatus.Job_completed: return True else: msg = job_object.getJobMessage() if msg: logger.error(f" [{job_name}] 错误: {msg}") return False if __name__ == "__main__": try: main() except KeyboardInterrupt: logger.info("\n\n用户中断脚本执行。") except Exception as e: logger.error(f"\n错误: {e}") logger.error("详细堆栈:", exc_info=True)

github仓库地址:https://github.com/Canon-create/contextcapture-python-example

参考文章:https://blog.csdn.net/qq_41475842/article/details/112959892

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/4 15:53:06

PHP微服务架构中的熔断器模式(从入门到生产级落地)

第一章&#xff1a;PHP微服务架构中熔断器模式概述在构建高可用的PHP微服务系统时&#xff0c;服务间的依赖调用可能因网络延迟、服务宕机或资源过载而引发连锁故障。熔断器模式&#xff08;Circuit Breaker Pattern&#xff09;作为一种容错机制&#xff0c;能够有效防止此类故…

作者头像 李华
网站建设 2026/1/7 5:41:38

NEU5ACΑ(2-6) N-聚糖:复杂糖蛋白结构与功能的关键解码者 1125602-44-9

NEU5ACΑ(2-6) N-聚糖&#xff0c;作为一种结构明确、高度分支的唾液酸化复合型N-聚糖&#xff0c;是现代糖生物学研究与生物医药开发领域至关重要的高端试剂。它不仅是解析生命过程中糖缀合物精密功能的分子探针&#xff0c;更是推动下一代糖蛋白药物、疫苗设计与疾病诊断技术…

作者头像 李华
网站建设 2026/1/10 12:11:58

PHP 8.7错误处理全面升级(前所未有的稳定性提升方案)

第一章&#xff1a;PHP 8.7错误处理全面升级概述PHP 8.7 在错误处理机制上进行了重大重构&#xff0c;旨在提升开发者体验、增强运行时健壮性&#xff0c;并统一异常与错误的处理路径。此次升级引入了更细粒度的错误分类、可恢复错误语义以及更清晰的堆栈追踪信息&#xff0c;使…

作者头像 李华
网站建设 2026/1/9 0:35:07

有用、自用、好玩的项目

1、快速生成mock数据&#xff1a; https://rtool.cn/jsonserver 简单使用&#xff1a; ①写一个1.json {"posts": [{ "id": 1, "title": "json-server", "author": "typicode" }],"comments": [{ "…

作者头像 李华
网站建设 2026/1/9 1:36:09

语音合成灰度用户参与式设计:邀请典型用户共创

语音合成灰度用户参与式设计&#xff1a;邀请典型用户共创 在智能语音助手讲着千篇一律的“标准音”、有声读物里的情感起伏生硬得如同机械朗读的今天&#xff0c;我们是否还能听到一个真正“像人”的声音&#xff1f;更进一步——能否让机器说出你自己的声音&#xff0c;带着你…

作者头像 李华
网站建设 2026/1/8 2:56:53

GLM-TTS在火山监测预警中的恶劣环境适应性改造

GLM-TTS在火山监测预警中的恶劣环境适应性改造 在菲律宾吕宋岛东海岸&#xff0c;马荣火山常年处于活跃状态。每当地震仪捕捉到异常震颤&#xff0c;或是气体传感器检测到SO₂浓度飙升时&#xff0c;时间就是生命——从数据识别到人群疏散的每一秒都至关重要。然而&#xff0c…

作者头像 李华