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

从CT到MRI:用RadiAnt DICOM Viewer的3D MPR和VR功能,5步搞定临床影像分析 本文详细介绍了如何利用RadiAnt DICOM Viewer的3D MPR和VR功能,通过五个步骤高效完成临床影像分析。从数据导入优化到三维重建技巧,再到精准测量与多模态数据融合,帮助医生快速掌握这一强大工具,提升CT和MRI影像诊断的准确性和效率。 阅读详情

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

1 概述

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

https://blog.youkuaiyun.com/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和edge2
	float3 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 × edge2
	float 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 · pvec
	float det = edge1.x * pvec_x + edge1.y * pvec_y + edge1.z * pvec_z;

	// 处理det为负的情况:交换v1和v2,反转法向量方向
	if (det < 0.0f) {
		// 交换v1和v2
		float3 temp = v1;
		v1 = v2;
		v2 = temp;

		// 重新计算edge1和edge2
		edge1 = 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和det
		pvec_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 × edge1
	float 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.youkuaiyun.com/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;
		else
			bounds[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,整个程序跑起来很顺滑,完全没有任何问题。

点云数据结构转换为 BVH 树及其应用 本文深入探讨了将点云数据结构转换为 BVH 树(Bounding Volume Hierarchy Tree)的原理、方法与应用。首先介绍了点云数据和 BVH 树的基本概念,详细阐述了转换算法的步骤,包括构建包围盒、递归划分等过程,并分别给出了 C#(使用 OpenTK 库)和 Python 的实现代码示例。最后探讨了转换后 BVH 树在碰撞检测、光线追踪、空间索引等方面的广泛应用,展示了其在计算机图形学、计算机视觉等领域的重要价值。 阅读详情

相关推荐

【信息科学与工程学】计算机科学与自动化——第八十六篇 各类应用上云计算 01

数字孪生:核心是实时性不等式(Δ<Δt)与带宽-精度权衡。数学上归结为带约束的非线性优化。预测性维护:从物理退化方程(Paris 定律)出发,结合贝叶斯推断,云端训练用深度时序模型,边缘推理用轻量模型。机器人协同:本质是多商品流 + 动态障碍物,需用图论(冲突检测)+ 最优控制(轨迹平滑)。工业视觉:延迟由四个环节组成,需用排队论分析流水线瓶颈,量化感知是降低延迟的关键。液压/气动系统上云(涉及流体力学、热力学、控制)机械臂力控/阻抗控制上云(涉及动力学、力/位混合控制)

weixin_49199313的博客 198

GAMES101: 现代计算机图形学入门(2)几何、光线追踪

注意:规定光线沿直线传播,不考虑其波动性,光线与光线之间不会碰撞,所有的光线可理解为光源射向我们的眼睛。我们将光线理解为从一个点出发,向某一方向发出的射线。注意:光线定义中的r(t)一定是一个点。

qq_45617648的博客 1003

详解三角面片:实现现实场景的数字化

详解三角面片:实现现实场景的数字化,含python示例程序。

叶子的博客 5854

dicom文件的值类型VR详解

dicom文件的值类型VR详解Dicom全称是医学数字图像与通讯,这里讲diocm格式文件的解读,读取本身是没啥难度的 无非就是字节码数据流处理。只不过确实比较繁琐。 dicom中文协议文档:http://download.youkuaiyun.com/download/wenzhi20102321/9897014

wenzhi的博客 2万+

三角面片化(用于图形的分割渲染等方面)

个人觉得还行吧..也是参考了其他人的程序..感谢原作者..希望对别人有点帮助..(本程序是为了学习,所以直接用console 来演示..)

从建模到调参:基于Simulink的两轮差速机器人轨迹跟踪PID控制全流程解析

本文详细解析了基于Simulink的两轮差速机器人轨迹跟踪PID控制全流程,涵盖运动学建模、仿真环境搭建、PID控制器设计与实现等关键环节。通过Simulink仿真和可视化调试技巧,帮助开发者掌握移动机器人运动控制的核心技术,提升轨迹跟踪精度与性能优化能力。

weixin_28717683的博客 240

python医学图像处理之三维点云模型特征提取

本文主要介绍三维点云模型的特征提取方法,本文并不止针对医学模型,可适用于所有的三维模型(例如,城市模型等)。

Cherry330的博客 2734

Android GNSS 可视卫星星空图/卫星天顶图 原理及画法介绍

目录1 概念介绍2 绘制步骤2.1 获取卫星信息2.2 绘制底图 效果图: 1 概念介绍 首先介绍一下方位角和高度角的概念: 方位角:是从某点的指北(地理北极)方向线起,依顺时针方向到目标方向线之间的水平夹角。 高度角:从一点至观测目标的方向线与水平面间的夹角。 卫星天顶图 即是根据每一颗卫星的方位角和高度角将其画在以观测位置为中心的天顶图上。 天顶图其底图为由外向内的三个圆和四条直线。三个圆由外向内依次代表高度角0°、30°、60°,中心点代表90°;四条直线分别表示正北-正南、东北-西南、正东

x1471898145的博客 1万+

Open3D的Renderer如何进行三维渲染(渲染原理)

如果是,则使用该三角面片的表面法线和颜色来更新当前像素的颜色值。片元着色是根据顶点着色时计算的数据(如顶点颜色、法线等)插值得到的,以便为每个像素提供合适的颜色值。通过将每个像素的颜色值根据三角面片的位置和属性进行更新,最终形成完整的图像。顶点着色还可以根据顶点的属性(如颜色、法线等)对顶点进行着色,为后续的渲染步骤提供必要的信息。光照参数包括光源的位置、光照颜色和强度等,相机参数包括视角、投影方式和相机位置等,材质参数包括物体表面的颜色、纹理和反射属性等。这些参数用于确定渲染时的视觉效果和物体的外观。

子燕若水的博客 1802

雷达仿真:FMCW TDMA-MIMO 3D点云获取方法

2 信号建模摘要:通过设置时分复用(TDMA)波形结合FMCW毫米波雷达仿真获取目标的距离、速度和方位角3D点云信息。在本案例中,首先通过发射天线交替发射FMCW波形并通过接收天线依次接收回波信号从而得到雷达原始数据,其次对获取的原始数据依次经行距离、速度、角度估计从而输出目标的3D点云。阅读本文档,你将对TDMA-MIMO雷达的基本原理、毫米波雷达距离估计、速度估计以及角度估计的基本原理有所了解,另外CA-CFAR恒虚警以及MUSIC-DOA估计在本案例中也有所涉及。1 TDMA-MIMO原理。

Poulen的博客 6958

基于cat12搞定SBM的ROI的统计分析——vertex水平和ROI水平的双样本T检验

前言 一、Atlas皮层图谱介绍 二、SBM分析——主要以皮层四种常用指标中的厚度(thickness)和沟深(depth)为主

Sophia2023的博客 8744

3D相机采集点云数据三维重建及可视化渲染(一)

在工业视觉检测中,使用线扫描或面扫描3D相机获取点云数据,对点云数据进行三维重建,获得被测对象的三维表面模型,同时对模型进行可视化渲染,从而更直观地观察被测表面。

qq_21428111的博客 2556

因内存不足原因导致fragment空白解决方案

在androidx之前,一般我们防止Fragment重建是在actvitity的onCreate方法中使用savedInstanceState?.remove(“android:support:fragments”)或者savedInstanceState?但是在androidx中,使用这两行代码不生效,但是看FragmentActivity中的FRAGMENT_TAG还是android:support:fragments,那为什么会不生效呢,后来点进去看发现。

qq_39431405的博客 546

osg三维重建的两种方法剖析:三角面片(osgUtil::DelaunayTriangulator)和四角面片(osg::HeightField)

最近项目中需要利用osg重建三维曲面,所以学习了一下。 第一,我先用的狄洛尼三角形的方法,即osgUtil::DelaunayTriangulator,用这种方法的特点是: 1.首先必须给其一个存储三维点集的数组,该方法会对这些杂乱无章的散点自动排序,然后就利用这些排好序的,符合三角网构建规则的散点去构建三角网,需要注意的是经过dt->setInputPointArray(coords);这句

菜鸟进化史 1万+

四足机器人如何用对抗运动先验(AMP)实现复杂地形行走?Unitree Go1实战解析

本文深入解析了四足机器人Unitree Go1如何通过对抗运动先验(AMP)算法实现复杂地形行走。AMP结合模仿学习与对抗训练,使机器人仅凭本体感知就能适应未知地形。文章详细介绍了AMP的核心架构、仿真训练工程实践以及在Unitree Go1上的部署优化方案,展示了其在极端地形下的卓越性能表现。

weixin_29050513的博客 327

04-协议基础03:USB 2.0信号与电气特性

USB 2.0使用两根差分信号线D+/D-进行数据传输,支持低速(1.5Mbps)、全速(12Mbps)和高速(480Mbps)三种速率。USB总线为主从模式,所有事务由Host发起。端电阻值作用HostRpd(D+)15KΩ±5%下拉,默认低电平HostRpd(D-)15KΩ±5%下拉,默认低电平全速设备Rpu(D+)1.5KΩ±5%上拉到3.3V,标识全速低速设备Rpu(D-)1.5KΩ±5%上拉到3.3V,标识低速USB 2.0硬件要点。

内核笔记 102

BVH动作文件读取以及使用OpenGL显示

使用OpenGL显示和加载BVH动作文件,该项目已经在我个人的Github上开源,项目地址:https://github.com/HW140701/LoadandDisplayBVH,欢迎大家批评指正和标星哦。

HW140701的博客 3760

LabVIEW实现HTTP通信

本节简要介绍HTTP通信的基础知识,并以工业物联网平台TLINK中云端设备的数据发布设计为例,阐述互联网环境下HTTP客户端应用程序设计的基本方法。重点讲解工业物联网平台TLINK中HTTP设备的创建、通信数据帧的构造与测试。

不脱发的程序猿 1543

Unity3D学习笔记1——绘制一个三角形

详细介绍了Unity3D的第一个简单实例——绘制一个三角形的过程。

Charlee44的技术驿站 3942

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

云胶片(云影像)

云影像(云胶片),已经存在多年,只不过由于现在有取代普通胶片的趋势,而胶片每年中国大约200亿-300亿的市场,所以,云胶片在火起来。我自己理解,胶片和“云胶片”没什么必然联系,只是有些人想叫它们有关系。其实,患者看不看这影像,没多大作用,关键是要影像科或者临床老师能方便快捷地看到另外一家医院的云胶片。所以,云胶片项目最好还是省、市牵头,否则,还是一个个信息孤岛。最近,我们做了iPad的应用,医生...

rendawei636专注医疗软件 2万+

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

付鼻窦瓦氏位与付鼻窦柯氏位区别

付鼻窦瓦氏位与付鼻窦柯氏位区别

rendawei636专注医疗软件 2万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Dicom中的Image Orientation/Position的理解和应用

在DICOM中,是通过Image Position和Image Orientation来描述当前的图像和人体坐标系的相对位置的。 打开DCM文件时,会发现下边的两个tag (0020,0032) DS ImagePosition(Patient) "-20.000000\-150.000000\150.000000 " (0020,0037) DS ImageOrientation(Pati...

rendawei636专注医疗软件 1万+

冠状动脉解剖(CTA)

以下的认知,也是通过其他的文章东挪西凑出来的,结合自己的理解归纳一下。后续还会更正,也望大家指正。 1 基本概念 左冠状动脉(Left Coronary artery) 左冠状动脉主干:Left Main Artery(LM) 左前降支Left Anterior descending artery(LAD)。沿着室间沟延伸,左前降可分为支近端 pLAD,中段mLAD,远端dLAD,延途发出第一对角支(Diagonal branchesD1)和第二对角支(D2)和前室间隔支对角支 中间支(RI):.

rendawei636专注医疗软件 1万+

云胶片(云影像)功能简介

1 首先,云胶片不能是这样的,这个只是换瓶不换药。这种图只是影像科老师在全序列图像中,选取的部分影像,患者没有拿到自己全部数据;另外,通过N-Print(胶片打印标准)打印的图像都是通过窗宽,窗位后的8bit的图像,原图有12bit,14bit,信息量损失太大。每当看到这种厂商在用这种产品去宣传,做项目,我就为其他的云胶片厂商鸣不平,这个就是jpeg显示,不但没有半点技术难度,而且存...

rendawei636专注医疗软件 1万+
上一篇: 五、医学影像云平台 - 医共体
下一篇: 六、影像云平台-质控-CT剂量统计
恒哥的爸爸
博客等级 码龄21年 1056粉丝 70原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值