学习机器视觉必须掌握的Visual C++数字图像处理基础

試著忘記壹切 提交于 2020-01-20 04:47:54

导读

机器视觉是当前最火热的人工智能分支之一,借助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);//显示图像
}

以下是运行结果
在这里插入图片描述

结果分析
可以看到图像发生了畸变,而且部分像素出现了丢失,这是因为图像大小和显示控件大小不一致,在对图像做几何变换时,我并没有设置像素插值函数。系统默认选择了最近的像素作为变换后的像素。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!