因为要做DICOM的图像显示,我要从文件中读图像的像素数组(0-4095)到内存中,然后显示在界面上。
这个过程需要将Uint16的DICOM像素格式转成Uint8的Bitmap像素格式。也即是把0-4095的灰度值转成0-255的灰度值并转换成Bitmap。
我做了两个版本的函数,代码在最后。
其中的e:\1.txt是我处理过的DICOM文件,仅仅是把图像的宽和高放在文件首,然后后面跟着像素数据而已。
但是我的代码运行时我发现了一个神奇的现象:
C++的程序显示出来的图像非常难看,这个是我意料之中的,因为我仅仅是把像素数据除以16而已,如此简单的操作当然应该得到如此粗糙的结果。
可是C#的程序运行之后,图像显示得很正常!正常得有点不正常了……
所以我想请问一下大家:明明是同样的像素数据(我对比过内存的),为什么C#中显示出来的样子和C++中显示出来的样子有如此差别,难道C#在显示的时候做了些什么其它的工作么?又或者是我的代码的问题?又或者是Bitmap这个类的关系?总之很不理解,求真相。读取文件显示图像代码C#版如下:请不要在意我脑残的注释
string fileName = @"e:\1.txt"; // 还是c#用着舒服啊,尤其是字符串,C++的那个能叫字符串? // 用这个东西来读取文件数据
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); byte[] bytes = new byte[4]; // 存放图像长和宽的数组 fs.Read(bytes, 0, 4);
Int16 nWidth = (Int16)ToObject(bytes, typeof(Int16)); // 把维数为4的BYTE型数组转成Int16的数字
fs.Read(bytes, 0, 4);
Int16 nHeight = (Int16)ToObject(bytes, typeof(Int16)); Bitmap bmp = new Bitmap(nWidth, nHeight); // 锁定图像对象的内存以进行读写,这个语句很常用。
BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); // 一个作为中转站的数组
byte[] pixels = new byte[nWidth * nHeight*4]; fs.Read(pixels, 0, nWidth * nHeight * 4); // 读取文件内容 Marshal.Copy(pixels, 0, data.Scan0, nWidth * nHeight * 4); // 拷贝到图像对象的内存中。 bmp.UnlockBits(data); // 显示图像,这个就没什么了。
panel1.BackgroundImageLayout = ImageLayout.Stretch;
panel1.BackgroundImage = bmp;以及C++版,同样请不要在意注释……
UINT16 nWidth;
UINT16 nHeight;CString fileName = "e:\\1.txt";
ifstream fileRead(fileName, ios::binary); // 输入二进制流BYTE* pData = (BYTE*)malloc(sizeof(UINT16)*4); // 缓存fileRead.read((char*)pData, sizeof(UINT16)*4); // 读取文件数据nWidth = *(((UINT16*)pData)); // 前8个字节是图像的宽和高
nHeight= *(((UINT16*)pData)+2); // 但是为什么这里要加2……free(pData); // C++ 的一个大问题就是一定要随时随地释放内存……pData = (BYTE*)malloc(sizeof(BYTE)*nWidth*nHeight*4); // 像素数据的缓存fileRead.read((char*)pData, sizeof(BYTE)*nWidth*nHeight*4); // 读取像素数据fileRead.close(); // 用完了要记得把输入流关闭,否则这个文件就再也打不开了……LPBITMAPINFOHEADER m_lpBMIH; // 这个结构是C++用来显示图像的,常用。
// 下面是这个结构的一般初始化方式
m_lpBMIH = (LPBITMAPINFOHEADER) new char
[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256]; // 分配内存
m_lpBMIH->biSize = sizeof(BITMAPINFOHEADER); // 这个值用于标记所占用内存的长度,常见。
m_lpBMIH->biWidth = nWidth; // 图像的宽度,不是扫描线宽度
m_lpBMIH->biHeight = nHeight; // 图像的高度。
m_lpBMIH->biBitCount = 32; // 像素格式,32表示RGBA,也可以取24、16等值
m_lpBMIH->biPlanes = 1; // 不用理会,直接赋这个值就好。
m_lpBMIH->biCompression = BI_RGB; // 不用理会,直接赋这个值就好。
m_lpBMIH->biSizeImage = 0; // 不用理会,直接赋这个值就好。
m_lpBMIH->biXPelsPerMeter = 0; // 不用理会,直接赋这个值就好。
m_lpBMIH->biYPelsPerMeter = 0; // 不用理会,直接赋这个值就好。void* pPixelData = (void*)pData; // 显示时要求使用VOID型的指针,这里转换一下看起来清楚一些// 得到窗口,C#里一个PANEL搞定的问题在C++里就这么麻烦……
CWnd* pWnd = this->GetDlgItem(IDC_PictureBox); // 窗口指针。IDC_PictureBox就是某个控件的ID啦
CDC* pDc = pWnd->GetDC(); // 窗口的显示上下文(表问我这是什么……)
HDC hdc = pDc->GetSafeHdc(); // 那个“上下文”的句柄(也表问我这是什么……)
CRect destRect; // 窗口对应的矩形
pWnd->GetWindowRect(&destRect); // 得到这个矩形……(麻烦啊……)
int destWidth = destRect.Width(); // 窗口的宽度和高度
int destHeight = destRect.Height();
// 绘图函数,C#里只需要告诉控件:把图给我画出来,而C++就得手把手的告诉控件这图怎么画……
StretchDIBits( hdc, // 就是那个“上下文”的句柄啦
0, 0,destWidth, destHeight, // 画图的矩形范围,我们传给它坐标原点和窗口矩形的长与宽
0, 0, nWidth, nHeight, // 数据的矩形范围,我们传给它坐标原点和图像的长与宽
pPixelData, // 像素数据的指针,这个是VOID*型的
(LPBITMAPINFO)m_lpBMIH, // 在前面初始化过的绘图参数结构
DIB_RGB_COLORS, SRCCOPY); // 这两个值指示如何绘图,一般不用去变free(pData); // 画完了图要把内存释放掉……
这个过程需要将Uint16的DICOM像素格式转成Uint8的Bitmap像素格式。也即是把0-4095的灰度值转成0-255的灰度值并转换成Bitmap。
我做了两个版本的函数,代码在最后。
其中的e:\1.txt是我处理过的DICOM文件,仅仅是把图像的宽和高放在文件首,然后后面跟着像素数据而已。
但是我的代码运行时我发现了一个神奇的现象:
C++的程序显示出来的图像非常难看,这个是我意料之中的,因为我仅仅是把像素数据除以16而已,如此简单的操作当然应该得到如此粗糙的结果。
可是C#的程序运行之后,图像显示得很正常!正常得有点不正常了……
所以我想请问一下大家:明明是同样的像素数据(我对比过内存的),为什么C#中显示出来的样子和C++中显示出来的样子有如此差别,难道C#在显示的时候做了些什么其它的工作么?又或者是我的代码的问题?又或者是Bitmap这个类的关系?总之很不理解,求真相。读取文件显示图像代码C#版如下:请不要在意我脑残的注释
string fileName = @"e:\1.txt"; // 还是c#用着舒服啊,尤其是字符串,C++的那个能叫字符串? // 用这个东西来读取文件数据
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); byte[] bytes = new byte[4]; // 存放图像长和宽的数组 fs.Read(bytes, 0, 4);
Int16 nWidth = (Int16)ToObject(bytes, typeof(Int16)); // 把维数为4的BYTE型数组转成Int16的数字
fs.Read(bytes, 0, 4);
Int16 nHeight = (Int16)ToObject(bytes, typeof(Int16)); Bitmap bmp = new Bitmap(nWidth, nHeight); // 锁定图像对象的内存以进行读写,这个语句很常用。
BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); // 一个作为中转站的数组
byte[] pixels = new byte[nWidth * nHeight*4]; fs.Read(pixels, 0, nWidth * nHeight * 4); // 读取文件内容 Marshal.Copy(pixels, 0, data.Scan0, nWidth * nHeight * 4); // 拷贝到图像对象的内存中。 bmp.UnlockBits(data); // 显示图像,这个就没什么了。
panel1.BackgroundImageLayout = ImageLayout.Stretch;
panel1.BackgroundImage = bmp;以及C++版,同样请不要在意注释……
UINT16 nWidth;
UINT16 nHeight;CString fileName = "e:\\1.txt";
ifstream fileRead(fileName, ios::binary); // 输入二进制流BYTE* pData = (BYTE*)malloc(sizeof(UINT16)*4); // 缓存fileRead.read((char*)pData, sizeof(UINT16)*4); // 读取文件数据nWidth = *(((UINT16*)pData)); // 前8个字节是图像的宽和高
nHeight= *(((UINT16*)pData)+2); // 但是为什么这里要加2……free(pData); // C++ 的一个大问题就是一定要随时随地释放内存……pData = (BYTE*)malloc(sizeof(BYTE)*nWidth*nHeight*4); // 像素数据的缓存fileRead.read((char*)pData, sizeof(BYTE)*nWidth*nHeight*4); // 读取像素数据fileRead.close(); // 用完了要记得把输入流关闭,否则这个文件就再也打不开了……LPBITMAPINFOHEADER m_lpBMIH; // 这个结构是C++用来显示图像的,常用。
// 下面是这个结构的一般初始化方式
m_lpBMIH = (LPBITMAPINFOHEADER) new char
[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256]; // 分配内存
m_lpBMIH->biSize = sizeof(BITMAPINFOHEADER); // 这个值用于标记所占用内存的长度,常见。
m_lpBMIH->biWidth = nWidth; // 图像的宽度,不是扫描线宽度
m_lpBMIH->biHeight = nHeight; // 图像的高度。
m_lpBMIH->biBitCount = 32; // 像素格式,32表示RGBA,也可以取24、16等值
m_lpBMIH->biPlanes = 1; // 不用理会,直接赋这个值就好。
m_lpBMIH->biCompression = BI_RGB; // 不用理会,直接赋这个值就好。
m_lpBMIH->biSizeImage = 0; // 不用理会,直接赋这个值就好。
m_lpBMIH->biXPelsPerMeter = 0; // 不用理会,直接赋这个值就好。
m_lpBMIH->biYPelsPerMeter = 0; // 不用理会,直接赋这个值就好。void* pPixelData = (void*)pData; // 显示时要求使用VOID型的指针,这里转换一下看起来清楚一些// 得到窗口,C#里一个PANEL搞定的问题在C++里就这么麻烦……
CWnd* pWnd = this->GetDlgItem(IDC_PictureBox); // 窗口指针。IDC_PictureBox就是某个控件的ID啦
CDC* pDc = pWnd->GetDC(); // 窗口的显示上下文(表问我这是什么……)
HDC hdc = pDc->GetSafeHdc(); // 那个“上下文”的句柄(也表问我这是什么……)
CRect destRect; // 窗口对应的矩形
pWnd->GetWindowRect(&destRect); // 得到这个矩形……(麻烦啊……)
int destWidth = destRect.Width(); // 窗口的宽度和高度
int destHeight = destRect.Height();
// 绘图函数,C#里只需要告诉控件:把图给我画出来,而C++就得手把手的告诉控件这图怎么画……
StretchDIBits( hdc, // 就是那个“上下文”的句柄啦
0, 0,destWidth, destHeight, // 画图的矩形范围,我们传给它坐标原点和窗口矩形的长与宽
0, 0, nWidth, nHeight, // 数据的矩形范围,我们传给它坐标原点和图像的长与宽
pPixelData, // 像素数据的指针,这个是VOID*型的
(LPBITMAPINFO)m_lpBMIH, // 在前面初始化过的绘图参数结构
DIB_RGB_COLORS, SRCCOPY); // 这两个值指示如何绘图,一般不用去变free(pData); // 画完了图要把内存释放掉……
BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
改成这个试试:
BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format16bppRgb);
int [,,] -> image;
可是我的问题是:C#的图是优化过的。
其实我只是想在C++里也这样优化一下。
然后如果能知道C#里是怎么优化的就再好不过了。