以下是 C++ 代码解析 DICOM 格式的医学影像文件并利用 OpenCV 显示图像的详细解释:

  1. 打开 DICOM 格式的医学影像文件

    FILE *fp;
    fp=fopen("11.dcm","rb");
    if(fp==NULL)
    {
        printf("can not open file!");
        return 0;
    }
    

    这段代码使用 fopen 函数以二进制读取模式打开名为 "11.dcm" 的 DICOM 文件。如果文件无法打开,则会输出错误信息并退出程序。

  2. 解析 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;
    }
    

    这段代码首先使用 fseekftell 函数获取 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
  3. 解析 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
  4. 根据解析到的信息,读取像素数据,并进行处理

    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 对象的三个通道。

  5. 将处理后的像素数据生成 OpenCV 的 Mat 对象,显示图像

    fclose(fp);
    cvNamedWindow("Dicomimage",0);
    imshow("Dicomimage",src);
    
    cvWaitKey(0);
    return 0;
    

    代码首先关闭 DICOM 文件,然后使用 OpenCV 的 cvNamedWindowimshow 函数创建窗口并显示图像。最后,使用 cvWaitKey 函数等待用户按键,按下任意键后程序退出。

代码中还包含了其他一些细节处理,例如:

  • 字节序判断:代码使用 isLitteEndian 变量来判断 DICOM 文件的字节序,根据字节序的不同,代码会以不同的方式读取像素数据。
  • 窗宽窗位调整:代码使用 windowsWidthwindowsCenter 变量来表示窗宽和窗位,并使用这些信息对像素值进行调整,使其处于 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;
}
C++ DICOM 文件解析与图像显示 - 基于 OpenCV 实现

原文地址: https://www.cveoy.top/t/topic/naCD 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录