数字图像处理实验报告
其他报告:
实验1:基本项目创建与RAW文件读取显示
实验名称 BMP图像文件的读取、显示和存储
实现BMP图像的读取、显示和存储
能够熟练使用Python进行编程
实现BMP图像的读取、显示和存储
加深对数字图像的理解
实验环境:Visual Studio 2019
所用语言:C++
一. 创建文件项目
创建项目与实验一类似,我本次实验沿用了第一次实验的基础项目,并在此之上进行DIB的实现。
创建好之后,新建一个类,命名为MyDIB,作为读写bmp文件的对象类。
二. 具体步骤
1. MyDIB.h:
先构建DIB的库文件:定义bmp文件头的结构体:
这里在定义之前加一个“#pragma pack(2)”来保证文件头和信息头的字节长度的正确读取。(因为默认4字节读取段)
#pragma once
#pragma pack(2)
typedef struct BMPFileHeader
{
WORD bfType; //"BM"
DWORD bfSize; //大小
WORD bfReserved1; //保留字
WORD bfReserved2; //保留字
DWORD bfOffBits; //偏移字节
} BitMapFileHeader;
typedef struct BMPInfoHeader
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BitMapInfoHeader;
typedef struct RGBQuad
{
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBquad;
typedef struct tagBitMapInfo
{
BitMapInfoHeader bminfoHeader;
RGBquad bminfoColors[1];
} BitMapInfo,*PBitMapInfo;
class MyDIB
{
public:
BMPFileHeader m_BMPfileheader;
BMPInfoHeader m_BMPinfoheader;
PBitMapInfo m_dibInfo;
BitMapInfo m_BMPInfo;
RGBQuad* Quad;
unsigned char *m_BMPdata;
int m_width;
int m_height;
double real_size;
int paletteSize;
public:
MyDIB();
~MyDIB();
void Read(const CString& filename);
void Write(const CString& filename,CDC *pDC,int width,int height);
};
这里我根据自己的需要新增了若干变量的定义。新增的:
tagBitMapInfo结构体是针对含有调色板的文件的头信息的读取结构。
m_BMPdata是用来记录文件的数据部分的。
PaletteSize是用来记录调色板的字节长度。
2. MyDIB.cpp:
这部分主要编写Read与Write函数:
Read()部分:
我采用的是CFile类进行文件读取,读取进来后通过一个文件指针来进行每一个字节数据的读取,首先要判断文件头中的前两个字节是不是“BM”,不是则不是bmp文件。
CFile fp(filename, CFile::modeRead | CFile::typeBinary);
//BitMapFileHeader m_BMPfileheader; //BMP文件头
//BitMapInfoHeader m_BMPinfoheader; //BMP信息头
ULONGLONG headpos; //文件指针
paletteSize = 0; //判断图像色彩位数
int ret, cbHeaderSize;
headpos = fp.GetPosition(); //指向文件头
ret = fp.Read(&m_BMPfileheader, sizeof(BitMapFileHeader)); //读取文件头
if (m_BMPfileheader.bfType != 0x4d42) //判断是不是“BM”,从而判断是不是bmp文件
{
AfxMessageBox(_T("不是BMP文件!!"));
return;
}
接下来读取信息头:
ret = fp.Read(&m_BMPinfoheader, sizeof(BitMapInfoHeader)); //读取信息头
switch (m_BMPinfoheader.biBitCount) { //判断色位
case 1:
paletteSize = 2;
break;
case 4:
paletteSize = 16;
break;
case 8:
paletteSize = 256;
break;
case 24:
paletteSize = 256*3;
break;
}
根据调色板的大小为信息头分配对应空间,并按照调色板的大小读入调色板数据:
cbHeaderSize = sizeof(BitMapInfoHeader) + paletteSize * sizeof(RGBquad);
m_dibInfo = (BitMapInfo*) new char[cbHeaderSize];
m_dibInfo->bminfoHeader = m_BMPinfoheader;
m_height = m_dibInfo->bminfoHeader.biHeight;
m_width = m_dibInfo->bminfoHeader.biWidth;
if (paletteSize) {
ret = fp.Read(&(m_dibInfo->bminfoColors[0]), paletteSize * sizeof(RGBquad));
if (ret != int(paletteSize * sizeof(RGBquad))) {
delete[] m_dibInfo;
m_dibInfo = NULL;
return;
}
Quad = m_dibInfo->bminfoColors;
}
最后,计算出真实有效数据区域的大小,并根据这个大小进行data数据的读取与存储,全部信息读取完成之后,关闭文件:
这里之所以要进行第一行的操作是因为要保证每一行的长度都是四的倍数。所以这里+31之后再/32是进行了一次取整的操作。
int bytesPerLine = ((m_dibInfo->bminfoHeader.biWidth * m_dibInfo->bminfoHeader.biBitCount + 31) / 32) * 4;
int bytesLength = bytesPerLine * m_dibInfo->bminfoHeader.biHeight;
real_size = bytesLength;
m_BMPdata = (unsigned char*) new char[bytesLength];
fp.Seek(headpos + m_BMPfileheader.bfOffBits, CFile::begin);
ret = fp.Read(m_BMPdata, bytesLength);
if (ret != int(bytesLength)) {
delete[] m_dibInfo;
delete[] m_BMPdata;
m_dibInfo = NULL;
m_BMPdata = NULL;
}
fp.Close();
Write部分:
与read类似,判断是否有调色板,并进行对应大小的数据读取,每一部分的数据必须对应到正确的区域中去,这里具体代码不再附,需要说的一点是,我在write函数中,还传入了CDC* pDC这个参数,实际上是通过它来获取当前对话框中的位图的信息,以下为读入参数后的初始化:
完整的MyDIB.cpp:
#include "pch.h"
#include "MyDIB.h"
MyDIB::MyDIB()
{
}
MyDIB::~MyDIB()
{
}
void MyDIB::Read(const CString& filename) {
CFile fp(filename, CFile::modeRead | CFile::typeBinary);
//BitMapFileHeader m_BMPfileheader; //BMP文件头
//BitMapInfoHeader m_BMPinfoheader; //BMP信息头
ULONGLONG headpos; //文件指针
paletteSize = 0; //判断图像色彩位数
int ret, cbHeaderSize;
headpos = fp.GetPosition(); //指向文件头
ret = fp.Read(&m_BMPfileheader, sizeof(BitMapFileHeader)); //读取文件头
if (m_BMPfileheader.bfType != 0x4d42) //判断是不是“BM”,从而判断是不是bmp文件
{
AfxMessageBox(_T("不是BMP文件!!"));
return;
}
ret = fp.Read(&m_BMPinfoheader, sizeof(BitMapInfoHeader)); //读取信息头
switch (m_BMPinfoheader.biBitCount) { //判断色位
case 1:
paletteSize = 2;
break;
case 4:
paletteSize = 16;
break;
case 8:
paletteSize = 256;
break;
case 24:
paletteSize = 256*3;
break;
}
/*为信息部分(信息头和调色板大小)分配空间*/
cbHeaderSize = sizeof(BitMapInfoHeader) + paletteSize * sizeof(RGBquad);
m_dibInfo = (BitMapInfo*) new char[cbHeaderSize];
m_dibInfo->bminfoHeader = m_BMPinfoheader;
m_height = m_dibInfo->bminfoHeader.biHeight;
m_width = m_dibInfo->bminfoHeader.biWidth;
if (paletteSize) {
ret = fp.Read(&(m_dibInfo->bminfoColors[0]), paletteSize * sizeof(RGBquad));
if (ret != int(paletteSize * sizeof(RGBquad))) {
delete[] m_dibInfo;
m_dibInfo = NULL;
return;
}
Quad = m_dibInfo->bminfoColors;
}
int bytesPerLine = ((m_dibInfo->bminfoHeader.biWidth * m_dibInfo->bminfoHeader.biBitCount + 31) / 32) * 4;
int bytesLength = bytesPerLine * m_dibInfo->bminfoHeader.biHeight;
real_size = bytesLength;
m_BMPdata = (unsigned char*) new char[bytesLength];
fp.Seek(headpos + m_BMPfileheader.bfOffBits, CFile::begin);
ret = fp.Read(m_BMPdata, bytesLength);
if (ret != int(bytesLength)) {
delete[] m_dibInfo;
delete[] m_BMPdata;
m_dibInfo = NULL;
m_BMPdata = NULL;
}
fp.Close();
}
void MyDIB::Write(const CString& bmpfile,CDC *pDC,int width,int height) {
CBitmap bitmap;
CDC memDC;
memDC.CreateCompatibleDC(pDC);
bitmap.CreateCompatibleBitmap(pDC, width, height);
memDC.SelectObject(&bitmap);
memDC.BitBlt(0, 0, width, height, pDC, 0, 0, SRCCOPY);
BITMAP bInfo;
bitmap.GetBitmap(&bInfo);
CFile file(bmpfile, CFile::modeCreate | CFile::modeWrite);
//计算调色板大小
int panelsize = 0;
if (bInfo.bmBitsPixel < 24) //非真彩色
{
panelsize = pow((double)2, bInfo.bmBitsPixel) * sizeof(RGBQUAD);
}
//定义位图信息
BITMAPINFO* bMapInfo = (BITMAPINFO*)LocalAlloc(LPTR, sizeof(BITMAPINFO) + panelsize);
bMapInfo->bmiHeader.biBitCount = bInfo.bmBitsPixel;
bMapInfo->bmiHeader.biClrImportant = 0;
bMapInfo->bmiHeader.biCompression = 0;
bMapInfo->bmiHeader.biHeight = bInfo.bmHeight;
bMapInfo->bmiHeader.biPlanes = bInfo.bmPlanes;
bMapInfo->bmiHeader.biSize = sizeof(BITMAPINFO);
bMapInfo->bmiHeader.biSizeImage = bInfo.bmHeight * bInfo.bmWidthBytes;
bMapInfo->bmiHeader.biWidth = bInfo.bmWidth;
bMapInfo->bmiHeader.biXPelsPerMeter = 0;
bMapInfo->bmiHeader.biYPelsPerMeter = 0;
//获取位图的实际数据
char* pData = new char[bMapInfo->bmiHeader.biSizeImage];
int len = GetDIBits(pDC->m_hDC, bitmap, 0, bInfo.bmHeight, pData, bMapInfo, DIB_RGB_COLORS);
BITMAPFILEHEADER bFileHeader;
bFileHeader.bfType = 0x4D42;
bFileHeader.bfReserved1 = 0;
bFileHeader.bfReserved2 = 0;
bFileHeader.bfSize = sizeof(BITMAPFILEHEADER);
bFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + panelsize;
//向文件中写入位图数据
file.Write(&bFileHeader, sizeof(BITMAPFILEHEADER));
file.Write(&bMapInfo->bmiHeader, sizeof(BITMAPINFOHEADER));
file.Write(pData, bMapInfo->bmiHeader.biSizeImage + panelsize);
file.Close();
delete pData;
LocalFree(bMapInfo);
bitmap.DeleteObject();
memDC.DeleteDC();
}
3. View.cpp:
在自己项目对应的View中添加调用:
// Test1View.cpp: CTest1View 类的实现
//
#include "pch.h"
#include "framework.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义,并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "Test1.h"
#endif
#include "Test1Doc.h"
#include "Test1View.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CTest1View
IMPLEMENT_DYNCREATE(CTest1View, CView)
BEGIN_MESSAGE_MAP(CTest1View, CView)
// 标准打印命令
ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
ON_COMMAND(ID_FILE_OPEN, &CTest1View::OnFileOpen)
ON_COMMAND(ID_FILE_SAVE_AS, &CTest1View::OnFileSaveAs)
END_MESSAGE_MAP()
// CTest1View 构造/析构
CTest1View::CTest1View() noexcept
{
// TODO: 在此处添加构造代码
m_pImage = NULL;
m_Nsize = 0;
m_Msize = 0;
m_dib = NULL;
}
CTest1View::~CTest1View()
{
if (m_pImage != NULL) {
delete[] m_pImage;
}
if (m_dib != NULL) {
delete m_dib;
m_dib = NULL;
}
}
BOOL CTest1View::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: 在此处通过修改
// CREATESTRUCT cs 来修改窗口类或样式
return CView::PreCreateWindow(cs);
}
// CTest1View 绘图
void CTest1View::OnDraw(CDC* pDC)
{
CTest1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CBitmap bmp;
if (!pDoc) {
return;
}
if (m_dib) {
int Count = (int)m_dib->m_dibInfo->bminfoHeader.biBitCount;
//printf("\n\nCOUNT == %d \n\n", Count);
if (Count == 8) {
unsigned grey;
for(int i = 0 ; i < m_dib->m_height ; i ++)
for (int j = 0; j < m_dib->m_width; j++) {
grey = *(m_dib->m_BMPdata + m_dib->m_width *(m_dib->m_height - 1 - i) + j);
pDC->SetPixelV(10 + j, 10 + i, RGB(grey, grey, grey));
}
}
else if (Count == 24) {
unsigned blue, red, green;
double len = m_dib->real_size / 3;
int width = (int)(len/m_dib->m_height);
for(int i = 0; i < m_dib->m_height ; i ++)
for (int j = 0; j < width ; j++) {
blue = *(m_dib->m_BMPdata + width * (m_dib->m_height - 1 - i) * 3 + j * 3 +2);
green = *(m_dib->m_BMPdata + width * (m_dib->m_height - 1 - i) * 3 + j * 3 +1 );
red = *(m_dib->m_BMPdata + width * (m_dib->m_height - 1 - i) * 3 + j * 3);
pDC->SetPixelV(10 + j, 10 + i, RGB(blue, green, red));
}
}
}
BOOL CTest1View::OnPreparePrinting(CPrintInfo* pInfo)
{
// 默认准备
return DoPreparePrinting(pInfo);
}
void CTest1View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: 添加额外的打印前进行的初始化过程
}
void CTest1View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: 添加打印后进行的清理过程
}
// CTest1View 诊断
#ifdef _DEBUG
void CTest1View::AssertValid() const
{
CView::AssertValid();
}
void CTest1View::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CTest1Doc* CTest1View::GetDocument() const // 非调试版本是内联的
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CTest1Doc)));
return (CTest1Doc*)m_pDocument;
}
#endif //_DEBUG
// CTest1View 消息处理程序
void CTest1View::OnFileOpen()
{
if (m_dib)
{
delete m_dib;
m_dib = NULL;
}
CFileDialog dlg(TRUE);
if (dlg.DoModal() == IDOK) {
CString filename = dlg.GetPathName();
m_dib = new MyDIB;
m_dib->Read(filename);
}
Invalidate();
}
void CTest1View::OnFileSaveAs()
{
// TODO: 在此添加命令处理程序代码
if (m_dib == NULL) {
return;
}
CDC* pDC = GetWindowDC();
CFileDialog fDlg(FALSE, _T("bmp"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("位图文件|*.bmp"), this);
if (fDlg.DoModal() == IDOK)
{
CString bmpfile = fDlg.GetPathName();
m_dib->Write(bmpfile,pDC,m_dib->m_width,m_dib->m_height);
}
/*m_dib->Write(filename,m_dib);*/
实验一代码
//DWORD ndwSizeRaw = m_Nsize * m_Msize;
//WriteRawFile(m_pImage, ndwSizeRaw);
return;
}
BOOL CTest1View::WriteRawFile(LPVOID lpvBits, DWORD ndwSizeRaw)
{
CFileDialog dlg(FALSE, _T("raw"), _T("*.raw"));
CFile RawFile;
if (dlg.DoModal() == IDOK) {
VERIFY(RawFile.Open(dlg.GetPathName(), CFile::modeCreate | CFile::modeWrite));
}
try {
RawFile.Write((LPVOID)lpvBits, ndwSizeRaw);
}
catch (CException* pe) {
pe->Delete();
AfxMessageBox(_T("write error!"));
RawFile.Close();
return FALSE;
}
RawFile.Close();
return TRUE;
return 0;
}
void OutputString(CString msg)
{
if (msg.IsEmpty() == false)
{
TRACE(msg);
TRACE("\n");
}
}
4. 在显示函数OnDraw中:
判断位图是否为真彩色,因为本次实验中只有256位图和24位真彩位图,所以智攀端count=8和24的情况。绘制函数与实验一类似,通过pixelVal函数一个像素一个像素地绘制。
if (m_dib) {
int Count = (int)m_dib->m_dibInfo->bminfoHeader.biBitCount;
//printf("\n\nCOUNT == %d \n\n", Count);
if (Count == 8) {
unsigned grey;
for(int i = 0 ; i < m_dib->m_height ; i ++)
for (int j = 0; j < m_dib->m_width; j++) {
grey = *(m_dib->m_BMPdata + m_dib->m_width *(m_dib->m_height - 1 - i) + j);
pDC->SetPixelV(10 + j, 10 + i, RGB(grey, grey, grey));
}
}
else if (Count == 24) {
unsigned blue, red, green;
double len = m_dib->real_size / 3;
int width = (int)(len/m_dib->m_height);
for(int i = 0; i < m_dib->m_height ; i ++)
for (int j = 0; j < width ; j++) {
blue = *(m_dib->m_BMPdata + width * (m_dib->m_height - 1 - i) * 3 + j * 3 +2);
green = *(m_dib->m_BMPdata + width * (m_dib->m_height - 1 - i) * 3 + j * 3 +1 );
red = *(m_dib->m_BMPdata + width * (m_dib->m_height - 1 - i) * 3 + j * 3);
pDC->SetPixelV(10 + j, 10 + i, RGB(blue, green, red));
}
}
实验结果:
总结
本次实验使用VC++,对于Bmp文件的灰度、彩色图像进行了读取、显示、存储的操作,加深了对于数字图像的理解。
另外,我还发现MFC项目中自带这bitmap类的定义,其实,本次实验中的显示、读取、存储操作,还有更简单快捷的解法,我将在下一次实验,实验三,直方图实验中进行实践。