C++ DICOM 文件解析与图像显示 - 基于 OpenCV 实现
以下是 C++ 代码解析 DICOM 格式的医学影像文件并利用 OpenCV 显示图像的详细解释:
-
打开 DICOM 格式的医学影像文件
FILE *fp; fp=fopen("11.dcm","rb"); if(fp==NULL) { printf("can not open file!"); return 0; }这段代码使用
fopen函数以二进制读取模式打开名为 "11.dcm" 的 DICOM 文件。如果文件无法打开,则会输出错误信息并退出程序。 -
解析 DICOM 文件头,获取图像的基本信息
fseek(fp,0,SEEK_END); file_length=ftell(fp); fseek(fp,0,SEEK_SET); fseek(fp,128,SEEK_SET); char headchar[5]; memset(headchar,0,5); int read_num = fread(headchar,1,4,fp); if(read_num!=4) { fclose(fp); return 0; } if(strcmp(headchar,"DICM")) { fclose(fp); printf("File is not DICM"); return 0; }这段代码首先使用
fseek和ftell函数获取 DICOM 文件的大小。然后,从文件偏移量 128 字节处开始读取 4 个字节,并判断这 4 个字节是否为 "DICM",以此来验证文件是否为 DICOM 格式。接下来,代码使用一个循环遍历 DICOM 文件头,解析每个 Tag 的值。
while(ftell(fp)+6<file_length) { TagValue tag; unsigned int len; memset(VR,0,3); fread(&tag,sizeof(TagValue),1,fp); int index=ftell(fp); // ... 解析 Tag 值 ... }在循环中,代码读取每个 Tag 的值,并根据 Tag 的类型进行不同的解析方式。例如,对于数据类型为 OB、OW 或 SQ 的 Tag,代码会先读取 2 个字节的 VR 字段,然后再读取 4 个字节的长度值。对于其他类型的 Tag,代码则会读取 2 个字节的长度值。
// ... 代码片段 ... else if(tag.tag1==0x28 && tag.tag2==0x103) { unsigned short m; fread(&m,sizeof(unsigned short),1,fp); if(m==0) { pixelType = ESourcePixelType_U16; } else if(m ==1) { pixelType = ESourcePixelType_I16; } } // ... 代码片段 ...这段代码片段解析了 Tag (0x0028, 0x103) 的值,该 Tag 代表像素数据类型。如果值为 0,则表示像素类型为 USHORT(无符号短整型),如果值为 1,则表示像素类型为 SHORT(有符号短整型)。
通过解析 Tag 值,代码获取了以下图像的基本信息:
- 图像的宽度:
cols - 图像的高度:
rows - 图像的通道数:
channle - 像素类型:
pixelType - 像素数据偏移量:
pixDataOffset
- 图像的宽度:
-
解析 DICOM 文件头,获取图像的窗宽、窗位、像素值的有效位数、像素值的长度等
// ... 代码片段 ... else if(tag.tag1==0x28&&tag.tag2==0x101) { fread(&validLen,sizeof(unsigned short),1,fp); } else if(tag.tag1==0x28&&tag.tag2==0x100) { fread(&dataLen,sizeof(unsigned short),1,fp); } else if(tag.tag1==0x28&&tag.tag2==0x1050) { char msg[11]; memset(msg,0,11); fread(msg,1,len,fp); windowsCenter=atoi(msg); } else if(tag.tag1==0x28,tag.tag2==0x1051) { //fseek(fp,len,SEEK_CUR); char msg[40]; memset(msg,0,40); fread(msg,1,len,fp); windowsWidth=atoi(msg); } // ... 代码片段 ...这段代码片段解析了以下 Tag 的值:
- Tag (0x0028, 0x101):像素值的有效位数(
validLen) - Tag (0x0028, 0x100):像素值的长度(
dataLen) - Tag (0x0028, 0x1050):窗位(
windowsCenter) - Tag (0x0028, 0x1051):窗宽(
windowsWidth)
- Tag (0x0028, 0x101):像素值的有效位数(
-
根据解析到的信息,读取像素数据,并进行处理
fseek(fp,pixDataOffset,SEEK_SET); // ... 代码片段 ... if(channle==1) { // ... 单通道图像处理 ... } else if(channle==3) { // ... 三通道图像处理 ... }代码首先使用
fseek函数将文件指针定位到像素数据偏移量处,然后根据通道数进行不同的处理:-
单通道图像处理
src.create((int)rows,(int)cols,CV_8UC1); src2.create((int)rows,(int)cols, CV_16SC1); for (int i = 0;i<rows;i++) { for(int j=0;j<cols;j++) { unsigned short gray=0; short gray2 = 0; unsigned char pix[2]; fread(pix,1,2,fp); if(pixelType == ESourcePixelType_U16) { // ... 处理无符号短整型像素数据 ... } else if(pixelType ==ESourcePixelType_I16) { // ... 处理有符号短整型像素数据 ... } // ... 处理像素值 ... src.at<uchar>(i,j)=nPixel; } }代码使用
fread函数读取像素数据,并根据像素类型进行不同的处理:- 如果像素类型为 USHORT,则代码会根据像素值的有效位数和字节序将 2 个字节的像素数据转换为一个无符号短整型。
- 如果像素类型为 SHORT,则代码会根据像素值的有效位数和字节序将 2 个字节的像素数据转换为一个有符号短整型。
代码会根据窗宽、窗位、像素值的有效位数和像素值的长度等信息对像素值进行调整,并根据像素值是否从 0 开始判定是否需要反转颜色。
-
三通道图像处理
src.create((int)rows,(int)cols,CV_8UC3); for (int i = 0;i<rows;i++) { for(int j=0;j<cols;j++) { unsigned char pix[3]; fread(pix,1,3,fp); src.at<Vec3b>(i,j)[0]=pix[2]; src.at<Vec3b>(i,j)[1]=pix[1]; src.at<Vec3b>(i,j)[2]=pix[0]; } }代码直接读取 3 个字节的像素数据,并将其分别赋值给 OpenCV Mat 对象的三个通道。
-
-
将处理后的像素数据生成 OpenCV 的 Mat 对象,显示图像
fclose(fp); cvNamedWindow("Dicomimage",0); imshow("Dicomimage",src); cvWaitKey(0); return 0;代码首先关闭 DICOM 文件,然后使用 OpenCV 的
cvNamedWindow和imshow函数创建窗口并显示图像。最后,使用cvWaitKey函数等待用户按键,按下任意键后程序退出。
代码中还包含了其他一些细节处理,例如:
- 字节序判断:代码使用
isLitteEndian变量来判断 DICOM 文件的字节序,根据字节序的不同,代码会以不同的方式读取像素数据。 - 窗宽窗位调整:代码使用
windowsWidth和windowsCenter变量来表示窗宽和窗位,并使用这些信息对像素值进行调整,使其处于 0 到 255 的范围内。 - 像素值有效位数判断:代码使用
validLen变量来表示像素值的有效位数,并根据有效位数的不同,代码会以不同的方式读取和处理像素数据。
通过以上步骤,C++ 代码成功解析了 DICOM 文件,并利用 OpenCV 显示了其中的图像。
完整代码:
// 11111.cpp : 定义控制台应用程序的入口点。
//
//#include <opencv2/opencv.hpp>
#include "highgui.h"
/*#pragma comment(lib, "opencv_core2411d.lib")
#pragma comment(lib, "opencv_imgproc2411d.lib")
#pragma comment(lib, "opencv_highgui2411d.lib")*/
using namespace cv;
using namespace std;
struct TagValue
{
unsigned short tag1;
unsigned short tag2;
};
// 原始象素类型
enum E_SourcePixelType
{
ESourcePixelType_U16, // USHORT
ESourcePixelType_I16, // SHORT
};
int main()
{
bool isVR=true;
bool isLitteEndian=true;
int file_length=0;
char VR[3];
unsigned int pixDataLen=0;
unsigned int pixDataOffset=0;
unsigned short channle=0;
unsigned short rows=0;
unsigned short cols=0;
unsigned short dataLen=0;
unsigned short validLen=0;
E_SourcePixelType pixelType;
int windowsWidth=0;
int windowsCenter=0;
bool ZeroIsBlack=true;
float RescaleSlope =0.06;
float RescaleIntercept=0;
FILE *fp;
fp=fopen("11.dcm","rb");
if(fp==NULL)
{
printf("can not open file!");
return 0;
}
fseek(fp,0,SEEK_END);
file_length=ftell(fp);
fseek(fp,0,SEEK_SET);
fseek(fp,128,SEEK_SET);
char headchar[5];
memset(headchar,0,5);
int read_num = fread(headchar,1,4,fp);
if(read_num!=4)
{
fclose(fp);
return 0;
}
if(strcmp(headchar,"DICM"))
{
fclose(fp);
printf("File is not DICM");
return 0;
}
while(ftell(fp)+6<file_length)
{
TagValue tag;
unsigned int len;
memset(VR,0,3);
fread(&tag,sizeof(TagValue),1,fp);
int index=ftell(fp);
if(tag.tag1==0x02)
{
fread(VR,1,2,fp);
if(!strcmp(VR,"OB")||!strcmp(VR,"OW")||!strcmp(VR,"SQ"))
{
fseek(fp,2,SEEK_CUR);
fread(&len,sizeof(unsigned int),1,fp);
}
else
{
unsigned short l;
int ss = fread(&l,sizeof(unsigned short),1,fp);
int a=ftell(fp);
len =(unsigned int)l ;
}
}
else if(tag.tag1==0xfffe)
{
if(tag.tag2==0xe000||tag.tag2==0xe00d||tag.tag2==0xe0dd)
{
fread(&len,sizeof(unsigned int),1,fp);
}
}
else if(isVR==true)
{
fread(VR,1,2,fp);
int a= ftell(fp);
if(!strcmp(VR,"OB")||!strcmp(VR,"OW")||!strcmp(VR,"SQ"))
{
fseek(fp,2,SEEK_CUR);
fread(&len,sizeof(unsigned int),1,fp);
}
else
{
unsigned short l;
l=sizeof(unsigned short);
fread(&l,sizeof(unsigned short),1,fp);
a= ftell(fp);
len =(unsigned int)l ;
}
}
else if(isVR==false)
{
fread(&len,sizeof(unsigned int),1,fp);
}
if(tag.tag1==0x02&&tag.tag2==0x10)
{
char msg[124];
memset(msg,0,124);
fread(msg,1,len,fp);
if(!strcmp(msg,"1.2.840.10008.1.2.1"))
{
isLitteEndian=true;
isVR=true;
}
else if(!strcmp(msg,"1.2.840.10008.1.2.2"))
{
isLitteEndian=false;
isVR=true;
}
else if(!strcmp(msg,"1.2.840.10008.1.2"))
{
isLitteEndian=true;
isVR=false;
}
}
else if(tag.tag1 ==0x28 && tag.tag2==0x103)
{
unsigned short m;
fread(&m,sizeof(unsigned short),1,fp);
if(m==0)
{
pixelType = ESourcePixelType_U16;
}
else if(m ==1)
{
pixelType = ESourcePixelType_I16;
}
}
else if(tag.tag1==0x7fe0&&tag.tag2==0x10)
{
pixDataLen=len;
pixDataOffset=ftell(fp);
fseek(fp,len,SEEK_CUR);
}
else if(tag.tag1==0x28&&tag.tag2==0x10)
{
fread(&rows,sizeof(unsigned short),1,fp);
}
else if(tag.tag1==0x28&&tag.tag2==0x11)
{
fread(&cols,sizeof(unsigned short),1,fp);
}
else if(tag.tag1==0x28&&tag.tag2==0x02)
{
fread(&channle,sizeof(unsigned short),1,fp);
}
else if(tag.tag1==0x28&&tag.tag2==0x101)
{
fread(&validLen,sizeof(unsigned short),1,fp);
}
else if(tag.tag1==0x28&&tag.tag2==0x100)
{
fread(&dataLen,sizeof(unsigned short),1,fp);
}
else if(tag.tag1==0x28&&tag.tag2==0x1050)
{
char msg[11];
memset(msg,0,11);
fread(msg,1,len,fp);
windowsCenter=atoi(msg);
}
else if(tag.tag1==0x28,tag.tag2==0x1051)
{
//fseek(fp,len,SEEK_CUR);
char msg[40];
memset(msg,0,40);
fread(msg,1,len,fp);
windowsWidth=atoi(msg);
}
else if(tag.tag1==0x0028&&tag.tag2==0x0004)
{
char msg[40];
memset(msg,0,40);
fread(msg,1,len,fp);
if(!strcmp(msg,"MONOCHROME1 "))
{
ZeroIsBlack=false;
}
else if(!strcmp(msg,"MONOCHROME2 "))
{
ZeroIsBlack=true;
}
}
else if(tag.tag1==0x0028&&tag.tag2==0x1052)
{
char msg[40];
memset(msg,0,40);
fread(msg,1,len,fp);
RescaleIntercept=atof(msg);
}
else if(tag.tag1==0x0028&&tag.tag2==0x1053)
{
char msg[40];
memset(msg,0,40);
fread(msg,1,len,fp);
RescaleSlope =atof(msg);
}
else
{
char msg[1024];
memset(msg,0,1024);
fread(msg,1,len,fp);
}
}
fseek(fp,pixDataOffset,SEEK_SET);
if(windowsCenter==0&&windowsWidth==0)
{
windowsWidth = 1 << validLen;
windowsCenter = windowsWidth / 2;
}
//int min_value,max_value;
//min_value=windowsCenter-windowsWidth/2.0+0.5;
//max_value=windowsCenter+windowsWidth/2.0+0.5;
//double pers = 255.0/(max_value-min_value);
Mat src;
Mat src2;
int nPixel= 0;
double fCtA = 0;
double fCtB = 0;
fCtA = (double)256 /windowsWidth;
fCtB = 128 - 256 * (double)windowsCenter / windowsWidth;
if (fCtB < 0)
{
fCtB = 0;
}
if (fCtB > 255)
{
fCtB = 255;
}
if(channle==1)
{
src.create((int)rows,(int)cols,CV_8UC1);
src2.create((int)rows,(int)cols, CV_16SC1);
for (int i = 0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
unsigned short gray=0;
short gray2 = 0;
unsigned char pix[2];
fread(pix,1,2,fp);
if(pixelType == ESourcePixelType_U16)
{
if(validLen<=8)
{
if(isLitteEndian)
{
gray=pix[0];
}
else
{
gray=pix[1];
}
}
else
{
long temp = 0;
if(isLitteEndian)
{
gray=*(unsigned short*)pix;
if(gray > 32767)
{
gray = 32767;
}
temp =gray*RescaleSlope+RescaleIntercept;
temp = temp * fCtA + fCtB;
}
else
{
gray=pix[1]+pix[0]*256;
temp =gray*RescaleSlope+RescaleIntercept;
temp = temp * fCtA + fCtB;
}
int nValue = (int)temp;
if(nValue>0xff)
{
nValue=0xff;
}
else if(nValue<0)
{
nValue=0;
}
nPixel = nValue;
}
}
else if(pixelType ==ESourcePixelType_I16)
{
if(validLen<=8)
{
if(isLitteEndian)
{
gray2=pix[0];
}
else
{
gray2=pix[1];
}
}
else
{
long temp = 0;
if(isLitteEndian)
{
gray2=*(short*)pix;
if(gray2 > 32767)
{
gray2 = 32767;
}
if(gray2 < -32767)
{
gray2 = -32767;
}
temp =gray2*RescaleSlope+RescaleIntercept;
temp = temp * fCtA + fCtB;
}
else
{
gray2=pix[1]+pix[0]*256;
temp =gray2*RescaleSlope+RescaleIntercept;
temp = temp * fCtA + fCtB;
}
int nValue = (int)temp;
if(nValue>0xff)
{
nValue=0xff;
}
else if(nValue<0)
{
nValue=0;
}
nPixel = nValue;
}
}
if(!ZeroIsBlack)
{
nPixel=255-nPixel;
}
src.at<uchar>(i,j)=nPixel;
}
//std::cout<<std::endl;
}
}
else if(channle==3)
{
src.create((int)rows,(int)cols,CV_8UC3);
for (int i = 0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
unsigned char pix[3];
fread(pix,1,3,fp);
src.at<Vec3b>(i,j)[0]=pix[2];
src.at<Vec3b>(i,j)[1]=pix[1];
src.at<Vec3b>(i,j)[2]=pix[0];
}
}
}
fclose(fp);
cvNamedWindow("Dicomimage",0);
imshow("Dicomimage",src);
cvWaitKey(0);
return 0;
}
原文地址: https://www.cveoy.top/t/topic/naCD 著作权归作者所有。请勿转载和采集!