医疗影像中,DICOM点云、三角面片实体混合渲染(VR)

此文章,涉及到专业性比较强,所以,大部分的内容,基本上都是示例代码的形式出现。以下的技术路径,完全经过实践验证,并且效果很好,可以放心使用。

1 概述

在医学影像中,对DICOM的渲染,通常采用Phong 冯氏光照模型来实现。具体细节可以参考我写的这篇文章

https://blog.csdn.net/rendaweibuaa/article/details/128910050?spm=1001.2014.3001.5501

这里只对一些VR渲染中,关键参数这里做一些补充。

1.1 渲染物体梯度计算 (N计算)

为了使沿着光线的方向显示的更平滑一下,梯度计算可以仿照滤波因子写一个梯度计算的方式;

1.2 阴影效果

为了显示效果更加逼真,近的物体更亮,远的物体更暗,这里采用距离的倒数作为亮度的权重。

上图明显可以看出来,近处亮远处暗的效果;

1.3 材质

因为DICOM中,没有材质属性,但是实际的渲染过程中,可以根据渲染骨,软组织来调整漫反射和镜面反射的参数。

2 STL格式的渲染

在工程中,通常有需要将STL格式和VR一起进行渲染的需求。例如假体的植入,例如牙科,关节置换等。STL格式这里就不具体展开介绍了。以下代码是,三角面片追踪渲染的代码;我把主要的注释都写在代码中。

/**
三角面片追踪渲染函数
输入是一个当前渲染前的rgba 的值
当前三角面的col
光线和三角片的交点 interactPoint
三角面片 trifaceV1 trifaceV2 trifaceV3
光线方向 dirLight
*/
__device__ float4 __trifaceTracing(float4 sum,  //累计颜色float4 col, // 颜色和透明度float alphaAccObject,float3 interactPoint, // 位置float3 trifaceV1,  // 三角面第一个顶点float3 trifaceV2,  // 三角面第二个顶点float3 trifaceV3,  // 三角面第三个顶点float3 dirLight,   // 光线方向bool invertZ,     float distanceInterAndO, // 交点与源点距离float vrBrightness       // 亮暗的权值)
{float3 v1 = trifaceV2 - trifaceV1;float3 v2 = trifaceV3 - trifaceV2;float3 N = cross(v1, v2);   // 法向量N = normalize(N);float diffuse = dot(N, dirLight);if (diffuse < 0)// 如果是表面渲染,光照在背面,不应该再有反射;但是对于当前的透视来说,直接N反过来;{diffuse = -1 * diffuse;}diffuse = diffuse < 5e-7f ? 1e-6f : diffuse;// Ka + Kd + Ks ≤ 1 这样可以避免光照过曝。不过,具体的比值可能因不同的材质和光照需求而有所不同float dr = distanceInterAndO + RAY_SOURCE_OFFSET; // RAY_SOURCE_OFFSET 用来调整距离,从而调整阴影效果float dr2 = pow(dr ,2);float invDr2 = 1.0f / dr2;float4 clrLight = col * 0.1f;// 以上已经将此点的Phong都计算出来float4 f4Temp = make_float4(0.0f);f4Temp = col * ( ((diffuse * 0.65f  + 0.15f  * (pow(diffuse, 64.0f))) * vrBrightness) * invDr2 );clrLight += f4Temp;float alphaWeightLeft = (1.0f - alphaAccObject) * col.w; // 给后续光线上的点,留下来的多少透明度(1.0f - alphaAccObject) 和 当前本身当前点的权值 乘return (sum + alphaWeightLeft * clrLight);
}

下面的代码是用来判断光线和三角面片是否相交

__device__ __forceinline__ bool rayIntersectTriangle(const float3& orig, const float3& dir,float3 v0, float3 v1, float3 v2, // 三角形的三个顶点float* t, float* u, float* v,float3* intersection)
{// 初始计算edge1和edge2float3 edge1 = make_float3(v1.x - v0.x, v1.y - v0.y, v1.z - v0.z);float3 edge2 = make_float3(v2.x - v0.x, v2.y - v0.y, v2.z - v0.z);// 计算pvec = dir × edge2float pvec_x = dir.y * edge2.z - dir.z * edge2.y;float pvec_y = dir.z * edge2.x - dir.x * edge2.z;float pvec_z = dir.x * edge2.y - dir.y * edge2.x;// 计算行列式det = edge1 · pvecfloat det = edge1.x * pvec_x + edge1.y * pvec_y + edge1.z * pvec_z;// 处理det为负的情况:交换v1和v2,反转法向量方向if (det < 0.0f) {// 交换v1和v2float3 temp = v1;v1 = v2;v2 = temp;// 重新计算edge1和edge2edge1 = make_float3(v1.x - v0.x, v1.y - v0.y, v1.z - v0.z);edge2 = make_float3(v2.x - v0.x, v2.y - v0.y, v2.z - v0.z);// 重新计算pvec和detpvec_x = dir.y * edge2.z - dir.z * edge2.y;pvec_y = dir.z * edge2.x - dir.x * edge2.z;pvec_z = dir.x * edge2.y - dir.y * edge2.x;det = edge1.x * pvec_x + edge1.y * pvec_y + edge1.z * pvec_z;}// 检查行列式是否接近零(考虑绝对值)if (fabsf(det) < 5e-7f){return false;}// 计算inv_det并继续后续步骤float inv_det = 1.0f / det;float3 tvec = make_float3(orig.x - v0.x, orig.y - v0.y, orig.z - v0.z);// 计算u并检查范围float local_u = (tvec.x * pvec_x + tvec.y * pvec_y + tvec.z * pvec_z) * inv_det;if (local_u < 0.0f || local_u > 1.0f) return false;// 计算qvec = tvec × edge1float qvec_x = tvec.y * edge1.z - tvec.z * edge1.y;float qvec_y = tvec.z * edge1.x - tvec.x * edge1.z;float qvec_z = tvec.x * edge1.y - tvec.y * edge1.x;// 计算v并检查范围float local_v = (dir.x * qvec_x + dir.y * qvec_y + dir.z * qvec_z) * inv_det;if (local_v < 0.0f || (local_u + local_v) > 1.0f) return false;// 计算t并检查正值float local_t = (edge2.x * qvec_x + edge2.y * qvec_y + edge2.z * qvec_z) * inv_det;if (local_t < 0.0f) return false;// 写入结果*t = local_t;*u = local_u;*v = local_v;// 计算交点坐标if (intersection) {intersection->x = orig.x + local_t * dir.x;intersection->y = orig.y + local_t * dir.y;intersection->z = orig.z + local_t * dir.z;}return true;
}

3 加速渲染(BVH树)

通过以上代码计算后,确实可以将三角面片和VR数据一次渲染出来。但是,当三角面片一多的时候,就会发现,渲染速度会变的非常慢。  

此时,我们可以通过创建Bvh树来加速渲染。

       原理可以参考https://blog.csdn.net/VIPCCJ/article/details/119550359,完全将时间复杂度从O(n)降低到O(logn)。

以下是创建BVH树的代码

// 计算包围盒
void BvhMethods::BvhCalculateBounds(const Facet3D* facets, int start, int end, float bounds[6]) {for (int i = 0; i < 6; ++i){if (i % 2 == 0)bounds[i] = FLT_MAX;elsebounds[i] = -FLT_MAX;}for (int i = start; i < end; ++i){const float* box = facets[i].boxes;for (int j = 0; j < 6; ++j){if (j % 2 == 0){if (box[j] < bounds[j])bounds[j] = box[j];}else{if (box[j] > bounds[j])bounds[j] = box[j];}}}
}// 合并两个包围盒
void BvhMethods::BvhMergeBounds(const float a[6], const float b[6], float out[6])
{for (int i = 0; i < 6; ++i) {out[i] = (i % 2 == 0) ? fminf(a[i], b[i]) : fmaxf(a[i], b[i]);}
}// 构建BVH
int BvhMethods::BvhBuildBVH(const std::vector<Facet3D>& facets, std::vector<BvhNode>& nodes, int start, int end) {int nodeIndex = nodes.size();nodes.push_back(BvhNode());// 计算当前节点的包围盒BvhCalculateBounds(&facets[0], start, end, nodes[nodeIndex].bounds);// 如果只有一个三角形,创建叶节点if (end - start == 1) {nodes[nodeIndex].left = -1;nodes[nodeIndex].right = -1;nodes[nodeIndex].start = start;nodes[nodeIndex].count = 1;return nodeIndex;}// 找到最佳分割轴float axisExtents[3];for (int axis = 0; axis < 3; ++axis) {float minVal = facets[start].boxes[axis];float maxVal = minVal;for (int i = start + 1; i < end; ++i) {minVal = fminf(minVal, facets[i].boxes[axis]);maxVal = fmaxf(maxVal, facets[i].boxes[axis]);}axisExtents[axis] = maxVal - minVal;}int splitAxis = 0;if (axisExtents[1] > axisExtents[splitAxis]) splitAxis = 1;if (axisExtents[2] > axisExtents[splitAxis]) splitAxis = 2;// 按分割轴排序三角形std::vector<int> indices(end - start);for (int i = 0; i < end - start; ++i) indices[i] = start + i;std::sort(indices.begin(), indices.end(), [splitAxis, &facets](int a, int b) {return facets[a].boxes[splitAxis] < facets[b].boxes[splitAxis];});// 找到中间点int middle = (start + end) * 0.5f;int split = middle;// 构建子节点int leftIndex = BvhBuildBVH(facets, nodes, start, middle);int rightIndex = BvhBuildBVH(facets, nodes, middle, end);nodes[nodeIndex].left = leftIndex;nodes[nodeIndex].right = rightIndex;nodes[nodeIndex].start = -1;nodes[nodeIndex].count = -1;return nodeIndex;
}

4 实验

下图是一个由59350个三角面片表示的左心室stl和整个心脏的DICOM点云数据,放在一起进行混合渲染效果图,笔记本上的显卡是RTX3050,整个程序跑起来很顺滑,完全没有任何问题。

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

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

相关文章

【C/C++】线程状态以及转换

文章目录 线程状态以及转换1 基本状态1.1 新建&#xff08;New&#xff09;1.2 就绪&#xff08;Ready / Runnable&#xff09;1.3 运行中&#xff08;Running&#xff09;1.4 阻塞/等待&#xff08;Blocked / Waiting / Sleeping&#xff09;1.5 挂起&#xff08;Suspended&am…

Python与自动驾驶数据集处理:构建智能驾驶的基石

Python与自动驾驶数据集处理:构建智能驾驶的基石 在自动驾驶技术的快速发展中,数据始终是最核心的驱动力。自动驾驶系统依赖于大量的传感器数据(激光雷达、摄像头、GPS等),通过深度学习算法不断优化决策,使车辆能够自主感知、理解道路环境并做出合理决策。而 Python 作为…

【菜狗work前端】小程序加if判断时不及时刷新 vs Web

零、前提&#xff1a; 实现input输入数字不大于10000&#xff08;需要配合typenumber&#xff0c;maxlength5&#xff0c;这里没写&#xff09; 一、探究代码&#xff1a; <input v-model"model1" input"changeModel1" placeholder"请输入拒收件…

【Netty】- NIO基础2

阻塞模式 客户端代码 public class Client {public static void main(String[] args) throws IOException {SocketChannel sc SocketChannel.open();sc.connect(new InetSocketAddress("localhost", 8080));// sc.write(Charset.defaultCharset().encode("he…

【WebRTC】源码更改麦克风权限

WebRTC源码更改麦克风权限 仓库: https://webrtc.googlesource.com/src.git分支: guyl/m125节点: b09c2f83f85ec70614503d16e4c530484eb0ee4f

cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins

cocos 版本2.4.11 在windows上jenkins的具体配置和部署&#xff0c;可参考上一篇文章cocos creator使用jenkins打包流程&#xff0c;打包webmobile_jenkins打包,发布,部署cocoscreator-CSDN博客 特别注意&#xff0c;windows上运行jenkins需要关闭windows自己的jenkins服务&a…

力扣刷题(第三十六天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 多数元素 解题思路 这道题是要找出数组中出现次数超过一半的元素。有几种不同的方法可以解决这个问题&#xff1a; 哈希表统计法&#xff1a;遍历数组&#xff0c;用哈希表统计每个元素的出现次数&#xff0c;…

关于读取CH584单片机的IO电平出现到的乌龙

本来是调用的库里的 uint8_t get_wake_up_sta (void) {return GPIOB_ReadPortPin(GPIO_Pin_10);//return cc_gpio_get_in_io (WAKUP_CH);} 然后读出来是0&#xff0c;我都配置上拉了。 搞不到原因。 最后是CH584单片机只有0和非零两种状态&#xff0c;读出来1024被转换成无…

Opencv常见学习链接(待分类补充)

文章目录 1.常见学习链接 1.常见学习链接 1.Opencv中文官方文档 2.Opencv C图像处理&#xff1a;矩阵Mat 随机数RNG 计算耗时 鼠标事件 3.Opencv C图像处理&#xff1a;亮度对比度饱和度高光暖色调阴影漫画效果白平衡浮雕羽化锐化颗粒感 4.OpenCV —— 频率域滤波&#xff…

anaconda、miniconda、conda的关系及miniconda安装

anaconda、miniconda、conda的关系及miniconda安装 文章目录 前言正文定义关系Linux安装miniconda新建一个python3.8环境 参考 前言 本文用于记录关于Anaconda、conda和Miniconda的定义及其关系的总结123&#xff1a; 正文 定义 conda 一个跨平台的开源包管理和环境管理工具…

2024-2025年AI领域重大事件深度解析:技术革命、产业重构与未来挑战

一、技术突破&#xff1a;从多模态到具身智能的跨越式演进 1. 生成式AI的“核爆级”升级 多模态融合&#xff1a;OpenAI的GPT-4o实现文本、图像、语音的实时交互&#xff0c;GPQA基准测试得分达87.7%&#xff0c;在科学推理和编程任务中表现卓越1。谷歌的Gemini 2.0 Flash支持…

城市地下“隐形卫士”:激光甲烷传感器如何保障燃气安全?

城市“生命线”面临的安全挑战 城市地下管网如同人体的“血管”和“神经”&#xff0c;承载着燃气、供水、电力、通信等重要功能&#xff0c;一旦发生泄漏或爆炸&#xff0c;将严重影响城市运行和居民安全。然而&#xff0c;由于管线老化、违规施工、监管困难等问题&#xff0…

融云 uni-app IMKit 上线,1 天集成,多端畅行

融云 uni-app IMKit 正式上线&#xff0c;支持一套代码同时运行在 iOS、Android、H5、小程序主流四端&#xff0c;集成仅需 1 天&#xff0c;并可确保多平台的一致性体验。 融云 uni-app IMKit 在 Vue 3 的高性能加持下开发实现&#xff0c;使用 Vue 3 Composition API&#x…

《Claude:人工智能界的璀璨新星》

一、Claude 登场&#xff1a;AI 新时代的震撼开篇 在科技飞速发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;已经成为推动社会进步和创新的核心力量。从智能语音助手到自动驾驶汽车&#xff0c;从图像识别技术到自然语言处理&#xff0c;AI 正以惊人的速度渗透到我…

Python中tqdm进度条工具和enumerate函数的使用详解

tqdm进度条工具 tqdm 是 Python 中一个非常流行的 进度条显示工具库&#xff0c;常用于迭代操作的可视化&#xff0c;比如训练神经网络、批量数据处理等任务。 一、tqdm 是什么&#xff1f; tqdm 全称是 taqaddum&#xff08;阿拉伯语&#xff0c;意为“进展”&#xff09;&a…

yum命令常用选项

刷新仓库列表 sudo yum repolist清理 Yum 缓存并生成新的缓存 sudo yum clean all sudo yum makecache验证 EPEL 源是否已正确启用 sudo yum repolist enabled安装软件包 sudo yum install <package-name> -y更新软件包 sudo yum update -y仅更新指定的软件包。 su…

linux debug技术

Linux是当今应用最广泛的免费和开源操作系统&#xff0c;它是一个复杂的分布式操作系统。它的内核的强大和灵活性已成为引用它的原因之一。在掌握Linux内核的过程中&#xff0c;调试工具可以帮助开发人员获得更深入的反思和理解。下面有25种不可或缺的Linux内核调试工具&#x…

【LinkedList demo 内部类讲说】

LinkedList demo 内部类讲说 1. Node节点2.MyLinkedList3. LinkedListTest 测试类 1. Node节点 public class Node<T> {private Node<T> pre;private Node<T> next;private T data;public Node() {}public Node getPre() {return pre;}public void setPre(N…

html主题切换小demo

主题切换功能为网页和应用程序提供了多样化的视觉风格与使用体验。实现多主题切换的技术方案丰富多样&#xff0c;其中 CSS 变量和 JavaScript 样式控制是较为常见的实现方式。 以下是一个简洁的多主题切换示例&#xff0c;愿它能为您的编程之旅增添一份趣味。 代码展示 <…

【数据结构】

一、架构梳理 线性&#xff08;1:1) 线性表 顺序存储 –> arr 链式存储 –> 指针 &#xff08;有头&#xff0c;无头&#xff09; 有头是指有一个不存数据的头&#xff0c;始终作为这个链表的起点。 会更加简单&#xff0c;无头的话&#xff0c;更改首部节点会麻烦。 头…