写点什么

DICOM 图像中灰度理解

用户头像
Lazy
关注
发布于: 2021 年 05 月 14 日
DICOM图像中灰度理解

背景

前段时间项目组开发了一款在线影像文件质控的 web 工具,当时研究员提出了这样一个需求,当点击图像的某一个点时,将这个点不同时间上的(MRI 扫描是持续一段时间的,扫描相同部位时,根据扫描频率就会产生多张图像)灰度值用折线图描绘出来。要想完成这个需求,首先就要弄清楚灰度值是什么。

理解含义



从百度百科上可以看出灰度通俗的讲就是用一个数字描述的黑白程度,值越小图片越暗。这样的理解对应想知道概念的人基本足够了,但是对于咱们 coding 的工程师,还需要进一步了解。


先来看看常见的的一些概念


RGB:我们生活中通过三原色深浅组成不同的颜色,前端开发中常用 RGB 来指定色彩,R、G、B 都是 0-255, 比如 (255,255,255) 表示白色,用 16 进制表示 255 就是 ff, 所以 255,255,255 可以表示为 ffffff, 在 css 样式表中写为 #ffffff。像素:图片我们都知道是由多个像素组成的,是图像的最小单元,如果这个单元里只有一个 bit 大小,也就是只有 0 和 1 两个值,由这种像素组成的图像称二值图像,显然它可以表示一个黑白图。这个单位里也可以存一个 16 位二进制数,24 位..., 可以想象存储的越大,一个像素能表达的颜色就越丰富,整体图像呈现就越精细,当然图片占用的空间也就越大。


1 位图像(即二值图像)只能表示黑白,那二位图像就可以表示 4 种颜色(00,01,10,11,VGA),依次类推,4 位(16 色,VGA),8 位(256 色),16 位(增强色),24 位和 32 位(真彩色)等。色彩图像:以 RGB 图像为例,RGB 图像每一个像素的颜色值(由 RGB 三原色表示)直接存放在图像矩阵中,由于每一像素的颜色需由 R、G、B 三个分量来表示,M、N 分别表示图像的行列数,三个 M x N 的二维矩阵分别表示各个像素的 R、G、B 三个颜色分量,假设我们在绘图的时候只取第一个矩阵,那么绘制出来的就是红色分量的图片。灰度图像:每个像素只有一个采样颜色,这类图像通常显示为从最暗黑色到最亮的白色的灰度,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。


灰度级: 灰度级表明图像中不同灰度的最大数量。灰度级越大,图像的亮度范围越大。二值图像一个像素只能存 0 和 1,它的灰度级就为 2,8 位图一个像素表示 0-255 共 256 个灰度值,所有它的灰度级为 256, 那么存储一张 512*512,256 级灰度的图片需要的空间就为 512*512*8=2,097,152 bit ≈ 1.7 Mb


图像的灰度化:


图像的灰度化是让像素点矩阵中的每一个像素点都满足关系:R=G=B,此时的这个值叫做灰度值。如 RGB(100,100,100)就代表灰度值为 100,RGB(50,50,50)代表灰度值为 50。


RGB 彩色图像一般灰度化处理的方法:

  • 浮点算法:Gray=R*0.3+G*0.59+B*0.11

  • 整数方法:Gray=(R*30+G*59+B*11)/100

  • 移位方法:Gray =(R*28+G*151+B*77)>>8

  • 平均值法:Gray=(R+G+B)/3

  • 仅取绿色:Gray=G

DICOM 中灰度

回顾背景中提到的需求,要想获取到灰度值,首先需要读取到 DICOM 中的像素数据,DICOM 标准中定义 PixelData(7FE0,0010)标签表示像素数据,以下使用 dcm4che 演示获取像素方法


File dcm = new File("xxx.dcm");DicomInputStream dis = new DicomInputStream(dcm);Attributes dataSet = dis.readDataset(-1, -1);byte[] pixelData = dataSet.getBytes(Tag.PixelData);
复制代码


获取到像素数据之后,还是无法确定某一点的灰度值,要实现这样的需要,还需要知道 DICOM 标准中定义的下面几个 Tag


  • Photometric Interpretation 该字段常见的值有 MONOCHROME1、MONOCHROME2、PALETTE COLOR、RGB

  • MONOCHROME1 和 MONOCHROME2 表示单通道灰度图像,只是两者对黑色和白色的映射相反而已;

  • PALETTE COLOR 就是 BMP 中提到的调色板图;

  • RGB 是常见的 R(红)、G(绿)、B(蓝)三通道彩色图像

  • Rows(0028,0010) 图像行数, 即高度

  • Columns(0028,0011) 图像列数,即宽度

  • BitsAllocated(0028,0100) 图像数据存储位数,一般是 8 位和 16 位

  • PixelRepresentation(0028,0103) 是否是带符号,0 是无符号,1 是有符号


System.out.println(dataSet.getString(Tag.PhotometricInterpretation));System.out.println(dataSet.getInt(Tag.BitsAllocated, 0));System.out.println(dataSet.getInt(Tag.PixelRepresentation, -1));System.out.println("rows: "+dataSet.getInt(Tag.Rows, 0));System.out.println("columns: "+dataSet.getInt(Tag.Columns, 0));System.out.println("pixelData length: " + pixelData.length);
复制代码


输出结果:


MONOCHROME2160rows: 900columns: 900pixelData length: 1620000
复制代码


从结果可以看出本例使用的是一张 16 位灰度图,像素为 900*900。16 位图一个像素使用两个字节(1 字节为 8bit),所以 900*900 总字节长度 = 900*900*2 = 1620000,与上面打印的结果也是一致的。假如现在要取第 10 行第 2 列的像素值,那么数组下标计算如下:


# 第10行第2列即 row=10,column=2index1 = ((row-1)*900 + column-1) * 2  =  16202index2 = index1 + 1 = 16203
复制代码


byte[] pixelData = dataSet.getBytes(Tag.PixelData);# 16位像素两个字节,  (java 默认都是BigEndian,所以第二个字节是高位)short pixel = (short) ((pixelData[16202] & 0xff) | ((pixelData[16203] & 0xff) << 8));System.out.println(pixel); 
复制代码


由于是灰度图像,取到的像素里的内容就是灰度值。至此,要实现最开始需求,只需要完成如下几步即可:


  • 获取用户的鼠标焦点

  • 通过一系列换算将焦点转为像素位置

  • 获取每个时间点图片相同位置的灰度值

  • 利用绘图软件绘出折线图


大概思路是这样的,本文主要探讨了灰度值的一种获取方式,部分内容参考了以下文章:


  1. DICOM入门(二)——图像

  2. 通过DCM4CHE获取dicom文件像素值

  3. DICOM图像像素值、灰度值与CT值

发布于: 2021 年 05 月 14 日阅读数: 21
用户头像

Lazy

关注

某知名软件上市公司基层软件开发。 2019.10.17 加入

助力于脑科学软件工程,为中国脑计划舔砖加瓦。 脑科学软件工程包含多种模态数据的处理分析(如脑影像、基因、行为评测等)、脑相关科研平台、研究成果转化。其应用可覆盖到医疗、教育、生活娱乐等各个领域。

评论

发布
暂无评论
DICOM图像中灰度理解