导读
机器视觉是当前最火热的人工智能分支之一,借助Halcon、Opencv等视觉处理库可以快速开发出适应不同场合的算法。其中Opencv是一款开源的视觉库,源码是用C++编写,目前已更新到4.0以上版本。其丰富的库函数和开源性吸引着众多视觉开发人员,可以说,没有编程基础和图像处理理论知识的人,也可以在短时间内掌握其用法,只要根据库函数帮助文档调整参数,调用即可。但如果没有图像处理基础,很难真正理解opencv的原理,更别说实现自己设计的图像处理算法。于是,我们必须掌握图像在计算机中的数据存储方式,下面就以BMP文件格式分析其存储结构。
BMP文件结构
BMP文件是Windows存储图像的数据格式,其结构由BITMAPFILEHEADER、BITMAPINFOHEADER、RGBQUAD、位图数据四部分组成。前三种是结构体数据类型,均包含在Windows.h中。位图数据是真正的图像灰度数据。下面借助于MSDN剖析前三种结构体数据。
BITMAPFILEHEADER
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
可以看到结构体种有五种数据元素,含义分别是:
bfType–位图文件类型,必须是0x424D,即字符“BM”的意思,占双字节。
bfSize–位图文件头大小,即14字节。
bfReserved1,bfReserved2–保留字节。
bfOffBits–位图文件头到位图数据的偏移量。
BITMAPINFOHEADER
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
其中含义分别是:
biSize–指定结构所需的字节数。
biWidth–指定位图的宽度,以像素为单位。
biHeight–指定位图的高度,以像素为单位。
biPlanes–指定目标设备的平面数。这个值必须设置为1。
biBitCount–指定每像素位的数目。
biCompression–指定自底向上压缩位图的压缩类型(不能压缩自顶向下的位图)。
biSizeImage–指定图像的大小(以字节为单位)。对于BI_RGB位图,这个值可以设置为0。
biXPelsPerMeter–指定位图目标设备的水平分辨率,单位为像素/米。应用程序可以使用此值从资源组中选择最符合当前设备特征的位图。
biYPelsPerMeter–指定位图目标设备的垂直分辨率,单位为像素/米。
biClrUsed–指定位图实际使用的颜色表中的颜色索引的数量。如果该值为零,则位图使用与双压缩指定的压缩模式的biBitCount成员值对应的最大颜色数。
biClrImportant–指定显示位图所需的颜色索引的数目。如果该值为0,则需要所有颜色。
RGBQUAD
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
rgbBlue–指定颜色中蓝色的强度。
rgbGreen–指定颜色中绿色的强度。
rgbRed–指定颜色中红色的强度。
rgbReserved–保留;必须是零。
基于MFC对话框编程实例
下面就以MFC对话框的形式新建一个读入图像并且显示图像的项目。
1. 基于MFC对话框新建项目
为对话框添加相应的控件及变量
2. 添加BmpCenter类
头文件
#pragma once
class BmpCenter
{
public:
BmpCenter();//构造函数
BmpCenter(CSize size, int nBitCount, LPRGBQUAD lpColorTable,
unsigned char *pImgData);//带参数的构造函数
~BmpCenter();//析构函数
public:
//图像数据指针
unsigned char * m_pImgData;
//图像颜色表指针
LPRGBQUAD m_lpColorTable;
//每像素占的位数
int m_nBitCount;
private:
//指向DIB的指针(包含BITMAPFILEHEADER,BITMAPINFOHEADER和颜色表)
LPBYTE m_lpDib;
//逻辑调色板句柄
HPALETTE m_hPalette;
//颜色表长度(多少个表项)
int m_nColorTableLength;
protected:
//图像的宽,像素为单位
int m_imgWidth;
//图像的高,像素为单位
int m_imgHeight;
//图像信息头指针
LPBITMAPINFOHEADER m_lpBmpInfoHead;
public:
//获取DIB的尺寸(宽高)
CSize GetDimensions();
//DIB读函数
BOOL Read(LPCTSTR lpszPathName);
//显示DIB
BOOL Draw(CDC* pDC, CPoint origin, CSize size);
//计算颜色表的长度
int ComputeColorTabalLength(int nBitCount);
private:
//创建逻辑调色板
void MakePalette();
//清理空间
void Empty();
};
.cpp文件
#include "stdafx.h"
#include "BmpCenter.h"
BmpCenter::BmpCenter()
{
m_lpDib = NULL;//初始化m_lpDib为空。
m_lpColorTable = NULL;//颜色表指针为空
m_pImgData = NULL; // 图像数据指针为空
m_lpBmpInfoHead = NULL; // 图像信息头指针为空
m_hPalette = NULL;//调色板为空
}
BmpCenter::BmpCenter(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char * pImgData)
{
//如果没有位图数据传入,我们认为是空的DIB,此时不分配DIB内存
if (pImgData == NULL) {
m_lpDib = NULL;
m_lpColorTable = NULL;
m_pImgData = NULL; // 图像数据
m_lpBmpInfoHead = NULL; // 图像信息头
m_hPalette = NULL;
}
else {//如果有位图数据传入
//图像的宽、高、每像素位数等成员变量赋值
m_imgWidth = size.cx;
m_imgHeight = size.cy;
m_nBitCount = nBitCount;
//根据每像素位数,计算颜色表长度
m_nColorTableLength = ComputeColorTabalLength(nBitCount);
//每行像素所占字节数,必须扩展成4的倍数
int lineByte = (m_imgWidth*nBitCount / 8 + 3) / 4 * 4;
//位图数据缓冲区的大小(图像大小)
int imgBufSize = m_imgHeight * lineByte;
//为m_lpDib一次性分配内存,生成DIB结构
m_lpDib = new BYTE[sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * m_nColorTableLength + imgBufSize];
//填写BITMAPINFOHEADER结构
m_lpBmpInfoHead = (LPBITMAPINFOHEADER)m_lpDib;
m_lpBmpInfoHead->biSize = sizeof(BITMAPINFOHEADER);
m_lpBmpInfoHead->biWidth = m_imgWidth;
m_lpBmpInfoHead->biHeight = m_imgHeight;
m_lpBmpInfoHead->biPlanes = 1;
m_lpBmpInfoHead->biBitCount = m_nBitCount;
m_lpBmpInfoHead->biCompression = BI_RGB;
m_lpBmpInfoHead->biSizeImage = 0;
m_lpBmpInfoHead->biXPelsPerMeter = 0;
m_lpBmpInfoHead->biYPelsPerMeter = 0;
m_lpBmpInfoHead->biClrUsed = m_nColorTableLength;
m_lpBmpInfoHead->biClrImportant = m_nColorTableLength;
//调色板句柄初始化为空,有颜色表时,MakePalette()函数要生成新的调色板
m_hPalette = NULL;
//如果有颜色表,则将颜色表拷贝进DIB的颜色表位置
if (m_nColorTableLength != 0) {
//m_lpColorTable指向DIB颜色表的起始位置
m_lpColorTable = (LPRGBQUAD)(m_lpDib + sizeof(BITMAPINFOHEADER));
//颜色表拷贝
memcpy(m_lpColorTable, lpColorTable, sizeof(RGBQUAD) * m_nColorTableLength);
//创建逻辑调色板
MakePalette();
}
//m_pImgData指向DIB位图数据起始位置
m_pImgData = (LPBYTE)m_lpDib + sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * m_nColorTableLength;
//拷贝图像数据进DIB位图数据区
memcpy(m_pImgData, pImgData, imgBufSize);
}
}
BmpCenter::~BmpCenter()
{
//释放m_lpDib所指向的内存缓冲区
if (m_lpDib != NULL)
delete[] m_lpDib;
//如果有调色板,释放调色板缓冲区
if (m_hPalette != NULL)
::DeleteObject(m_hPalette);
}
CSize BmpCenter::GetDimensions()
{
if (m_lpDib == NULL) return CSize(0, 0);
return CSize(m_imgWidth, m_imgHeight);
}
BOOL BmpCenter::Read(LPCTSTR lpszPathName)
{
//读模式打开图像文件
CFile file;
if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite))
return FALSE;
BITMAPFILEHEADER bmfh;
try {
//清理空间
Empty();
//读取BITMAPFILEHEADER结构到变量bmfh中
int nCount = file.Read((LPVOID)&bmfh, sizeof(BITMAPFILEHEADER));
//异常判断
if (nCount != sizeof(BITMAPFILEHEADER)) {
throw new CInvalidArgException;
}
if (bmfh.bfType != 0x4d42) {
throw new CInvalidArgException;
}
//为m_lpDib分配空间,读取DIB进内存
if (m_lpDib != NULL)
delete[]m_lpDib;
m_lpDib = new BYTE[file.GetLength() - sizeof(BITMAPFILEHEADER)];
file.Read(m_lpDib, file.GetLength() - sizeof(BITMAPFILEHEADER));
//m_lpBmpInfoHead位置为m_lpDib起始位置
m_lpBmpInfoHead = (LPBITMAPINFOHEADER)m_lpDib;
//为成员变量赋值
m_imgWidth = m_lpBmpInfoHead->biWidth;
m_imgHeight = m_lpBmpInfoHead->biHeight;
m_nBitCount = m_lpBmpInfoHead->biBitCount;
//计算颜色表长度
m_nColorTableLength =
ComputeColorTabalLength(m_lpBmpInfoHead->biBitCount);
//如果有颜色表,则创建逻辑调色板
m_hPalette = NULL;
if (m_nColorTableLength != 0) {
m_lpColorTable = (LPRGBQUAD)(m_lpDib + sizeof(BITMAPINFOHEADER));
MakePalette();
}
//m_pImgData指向DIB的位图数据起始位置
m_pImgData = (LPBYTE)m_lpDib + sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * m_nColorTableLength;
}
catch (CException* pe) {
AfxMessageBox(TEXT("Read error"));
pe->Delete();
return FALSE;
}
//函数返回
return TRUE;
}
BOOL BmpCenter::Draw(CDC * pDC, CPoint origin, CSize size)
{
//旧的调色板句柄
HPALETTE hOldPal = NULL;
//如果DIB为空,则返回0
if (m_lpDib == NULL) return FALSE;
//如果DIB有调色板
if (m_hPalette != NULL) {
//将调色板选进设备环境中
hOldPal = ::SelectPalette(pDC->GetSafeHdc(), m_hPalette, TRUE);
//实现调色板
pDC->RealizePalette();
}
//设置位图伸缩模式
pDC->SetStretchBltMode(COLORONCOLOR);
//将DIB在pDC所指向的设备上进行显示
::StretchDIBits(pDC->GetSafeHdc(), origin.x, origin.y, size.cx, size.cy,
0, 0, m_lpBmpInfoHead->biWidth, m_lpBmpInfoHead->biHeight, m_pImgData,
(LPBITMAPINFO)m_lpBmpInfoHead, DIB_RGB_COLORS, SRCCOPY);
//恢复旧的调色板
if (hOldPal != NULL)
::SelectPalette(pDC->GetSafeHdc(), hOldPal, TRUE);
//函数返回
return TRUE;
}
int BmpCenter::ComputeColorTabalLength(int nBitCount)
{
int colorTableLength;
switch (nBitCount) {
case 1:
colorTableLength = 2;
break;
case 4:
colorTableLength = 16;
break;
case 8:
colorTableLength = 256;
break;
case 16:
case 24:
case 32:
colorTableLength = 0;
break;
default:
ASSERT(FALSE);
}
ASSERT((colorTableLength >= 0) && (colorTableLength <= 256));
return colorTableLength;
}
void BmpCenter::MakePalette()
{
//如果颜色表长度为0,则不创建逻辑调色板
if (m_nColorTableLength == 0)
return;
//删除旧的逻辑调色板句柄
if (m_hPalette != NULL) ::DeleteObject(m_hPalette);
//申请空间,根据颜色表生成LOGPALETTE结构
LPLOGPALETTE pLogPal = (LPLOGPALETTE) new char[2 * sizeof(WORD) +
m_nColorTableLength * sizeof(PALETTEENTRY)];
pLogPal->palVersion = 0x300;
pLogPal->palNumEntries = m_nColorTableLength;
LPRGBQUAD m_lpDibQuad = (LPRGBQUAD)m_lpColorTable;
for (int i = 0; i < m_nColorTableLength; i++) {
pLogPal->palPalEntry[i].peRed = m_lpDibQuad->rgbRed;
pLogPal->palPalEntry[i].peGreen = m_lpDibQuad->rgbGreen;
pLogPal->palPalEntry[i].peBlue = m_lpDibQuad->rgbBlue;
pLogPal->palPalEntry[i].peFlags = 0;
m_lpDibQuad++;
}
//创建逻辑调色板
m_hPalette = ::CreatePalette(pLogPal);
//释放空间
delete pLogPal;
}
void BmpCenter::Empty()
{
//释放DIB内存缓冲区
if (m_lpDib != NULL) {
delete[] m_lpDib;
m_lpDib = NULL;
m_lpColorTable = NULL;
m_pImgData = NULL;
m_lpBmpInfoHead = NULL;
}
//释放逻辑调色板缓冲区
if (m_hPalette != NULL) {
::DeleteObject(m_hPalette);
m_hPalette = NULL;
}
}
3. 结果展示
在BmpHandleDlg.cpp文件中添加点击事件处理函数
别忘了在BmpHandleDlg.h中添加BmpCenter类的头文件
void CBmpHandleDlg::OnBnClickedButton1()
{
CString str;
m_edit.GetWindowTextW(str);
CDC *pDC; //添加环境设备指针,用于获取picture控件
pDC = m_picture.GetDC();
CRect rect;
m_picture.GetClientRect(&rect);//获取控件大小
CSize size;
size.cx = rect.Width();//图像水平尺寸根据控件宽度设定
size.cy = rect.Height();//图像垂直尺寸根据控件高度设定
m_Bmp.Read(str);//读取图像
m_Bmp.Draw(pDC, (0, 0), size);//显示图像
}
以下是运行结果
结果分析
可以看到图像发生了畸变,而且部分像素出现了丢失,这是因为图像大小和显示控件大小不一致,在对图像做几何变换时,我并没有设置像素插值函数。系统默认选择了最近的像素作为变换后的像素。
来源:CSDN
作者:1+1>2?continue:break
链接:https://blog.csdn.net/qq_42686324/article/details/103789616