数字图像处理2(C++)
有话在先
请跟随博主一起学习“数字图像处理技术”!
这里开了一个“图像处理”的专栏,博主会根据自己的学习进度及时地将相关的收获分享给大家。那么,博主选用的开发语言是什么呢?在一开始的时候,博主原本想使用M语言,并且还借了一本相关的书——continue——.
摆好龙门阵
visual studio 2019 + mfc + photo(bmp)。可以参考微软的官方文档https://docs.microsoft.com/en-us/windows/win32/api/_gdi/。
如何把 JPG 和 PNG 等图像格式转化为 BMP 格式?简单的操作方法如下:打开“画图”,
然后打开图片:左键点击【文件】→【打开】,在弹出的对话框中找到要打开的图像:
然后,另存为“bmp”格式的图像。左键点击【文件】→【另存为】→【BMP图片】,在弹出的对话框中找到要打开的图像:
接下来的操作就不展示了。
代码走起
不急,先使用VS创建一个空项目。
然后在“头文件”里创建三个头文件,分别为:BMP.h、eightbitmap.h、twentyfourbitmap.h。接着在源文件里,创建main.cpp文件。并把下载好的(或通过上述方式转换好的bmp)图像与代码放在一起。
在BMP.h头文件里,写下述代码:
#pragma once #ifndef BMP_H//预处理器 #define BMP_H typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned int DWORD; typedef long LONG; //BMP文件头(14字节) typedef struct tagBITMAPFILEHEADER { //WORD bfType;//位图文件的类型,必须为BM(在结构体中读取会发生错误,所以在函数中读取) DWORD bfSize;//位图文件的大小,以字节为单位 WORD bfReserved1;//位图文件保留字,必须为0 WORD bfReserved2;//位图文件保留字,必须为0 DWORD bfOffBits;//位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位 }BITMAPFILEHEADER; //BMP信息头(40字节) typedef struct tagBITMAPINFOHEADER { DWORD biSize;//本结构所占用字节数 LONG biWidth;//位图的宽度,以像素为单位 LONG biHeight;//位图的高度,以像素为单位 WORD biPlanes;//目标设备的级别,必须为1 WORD biBitCount;//每个像素所需的位数,必须是1(双色),4(16色),8(256色)16(高彩色)或24(真彩色)之一 DWORD biCompression;//位图压缩类型,必须是0(不压缩),1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一 DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位 LONG biXPelsPerMeter;//位图水平分辨率,每米像素数 LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数 DWORD biClrUsed;//位图实际使用的颜色表中的颜色数 DWORD biClrImportant;//位图显示过程中重要的颜色数 }BITMAPINFOHEADER; //调色板 //只有8位位图才用调色板,用像素值作为索引(0~255),调色板中RGB值都是一样的,范围是0~255 //一个unsigned char的范围刚好是0~255,所以用BYTE类型存储8位位图的像素 typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved;//保留,必须为0 }RGBQUAD; //像素信息 //8位BMP图1个字节代表一个像素,所以可以不用结构存储像素素组,直接用一个指针即可 typedef struct tagIMAGEDATA { BYTE blue; BYTE green; BYTE red; }IMAGEDATA; #endif
在eightbitmap.h文件里,写下面代码:
#pragma once #ifndef EIGHTBITMAP_H//预处理器 #define EIGHTBITMAP_H #include<iostream> #include"BMP.h" using namespace std; class eightBitMap { private: char imageName[30];//图像名 int width, height;//图像的宽高 BITMAPFILEHEADER bmpHead;//文件头 BITMAPINFOHEADER bmpInfo;//信息头 BYTE* imagedata = NULL, * newimagedata = NULL;//存储图片像素信息的二维数组 RGBQUAD* pallet = new RGBQUAD[256];//调色板指针 FILE* fpin, * fpout;//文件指针 //平滑算子也是通过模板进行处理的,所以可以把平滑处理和锐化处理通过一个函数实现 int Template1[3][3]{ 1,1,1,1,1,1,1,1,1 };//平滑模板 int Template2[3][3]{ 0,-1,0,-1,5,-1,0,-1,0 };//laplace锐化模板,4邻域(原图减去轮廓) int Template3[3][3]{ -1,-1,-1,-1,9,-1,-1,-1,-1 };//laplace锐化模板,8邻域 public: bool readImage();//读取图片 bool writeImage();//保存图片 bool Operation(int x);//图像平滑和锐化处理 bool Operation(int Template[][3], int coefficient);//图像处理 bool Binarization();//二值化 void showBmpHead(BITMAPFILEHEADER BmpHead);//显示文件头 void showBmpInfo(tagBITMAPINFOHEADER BmpInfo);//显示信息头 }; bool eightBitMap::readImage() { cout << "输入要读取的图片名:"; cin >> imageName; if (!fopen_s(&fpin, imageName, "rb")) { //读取图片类型 WORD bfType; fread(&bfType, sizeof(WORD), 1, fpin);//fread()的使用 if (bfType != 0x4d42) { cout << "该图片不是BMP!" << endl; return false; } //读取文件头和信息头 fread(&bmpHead, sizeof(tagBITMAPFILEHEADER), 1, fpin); fread(&bmpInfo, sizeof(tagBITMAPINFOHEADER), 1, fpin); //检查是否是8位位图 if (bmpInfo.biBitCount != 8) { cout << "该图片不是8位!" << endl; return false; } //读取调色板 fread(pallet, sizeof(RGBQUAD), 256, fpin); //读取图片的像素信息 width = bmpInfo.biWidth; height = bmpInfo.biHeight; width = (width * sizeof(BYTE) + 3) / 4 * 4;//图像的每一行必须是4的整数倍 imagedata = new BYTE[width * height]; fread(imagedata, sizeof(BYTE) * width, height, fpin); //显示文件头和信息头 showBmpHead(bmpHead); showBmpInfo(bmpInfo); //关闭图片 fclose(fpin); } else { cout << "图片不存在!" << endl; return false; } return true; } bool eightBitMap::writeImage() { char imageName[30]; cout << "输入处理后的位图名:"; cin >> imageName; //创建位图文件 if (fopen_s(&fpout, imageName, "wb")) { cout << "创建文件失败!" << endl; return false; } //写入位图类型 WORD bfBYTE = 0x4d42; fwrite(&bfBYTE, 1, sizeof(WORD), fpout); //写入文件头和信息头 fwrite(&bmpHead, 1, sizeof(BITMAPFILEHEADER), fpout); fwrite(&bmpInfo, 1, sizeof(BITMAPINFOHEADER), fpout); //写入调色板 fwrite(pallet, sizeof(RGBQUAD), 256, fpout); //写入图像像素数据 fwrite(newimagedata, sizeof(BYTE) * width, height, fpout); //关闭图片 fclose(fpout); //释放内存 delete imagedata; delete newimagedata; delete pallet; return true; } bool eightBitMap::Operation(int x) { if (x == 1) return Operation(Template1, 9); else if (x == 2) return Operation(Template2, 1); else if (x == 3) return Operation(Template3, 1); else { cout << "模板调用错误!" << endl; return false; } } bool eightBitMap::Operation(int Template[][3], int coefficient) { //分配新像素素组的空间 newimagedata = new BYTE[width * height]; //进行模板操作 for (int i = 0; i < height; ++i) for (int j = 0; j < width; ++j) { if (i == 0 || j == 0 || i == height - 1 || j == width - 1) *(newimagedata + i * width + j) = *(imagedata + i * width + j); else { int sum = 0; for (int m = i - 1; m < i + 2; ++m) for (int n = j - 1; n < j + 2; ++n) { sum += (*(imagedata + m * width + n)) * Template[n - j + 1][m - i + 1] / coefficient; } //8位BMP中一个像素值,对应调色板中索引号为该像素值的项所存放的RGB色彩 //所以像素值范围为0~255 //像素值小于0就取0,大于255就取255 sum = (sum >= 0) ? sum : 0; sum = ((sum + *(imagedata + i * width + j)) > 255) ? 255 : sum; *(newimagedata + i * width + j) = sum; } } //保存图片 if (writeImage()) { cout << "\n处理成功!" << endl; return true; } else { cout << "\n处理失败!" << endl; return false; } } bool eightBitMap::Binarization() { //分配新像素素组的空间 newimagedata = new BYTE[width * height]; //二值化操作 for (int i = 0; i < width * height; ++i) if (*(imagedata + i) > 128) *(newimagedata + i) = 255; else *(newimagedata + i) = 0; //保存图片 if (writeImage()) { cout << "\n处理成功!" << endl; return true; } else { cout << "\n处理失败!" << endl; return false; } } void eightBitMap::showBmpHead(BITMAPFILEHEADER BmpHead) { cout << "\n图片文件头:" << endl; cout << " 图片大小:" << BmpHead.bfSize << endl; cout << " 保留字_1:" << BmpHead.bfReserved1 << endl; cout << " 保留字_2:" << BmpHead.bfReserved2 << endl; cout << " 实际位图片数据的偏移字节数:" << BmpHead.bfOffBits << endl; } void eightBitMap::showBmpInfo(tagBITMAPINFOHEADER BmpInfo) { cout << "图片信息头:" << endl; cout << " 结构体的长度:" << BmpInfo.biSize << endl; cout << " 位图宽:" << BmpInfo.biWidth << endl; cout << " 位图高:" << BmpInfo.biHeight << endl; cout << " 平面数:" << BmpInfo.biPlanes << endl; cout << " 采用颜色位数:" << BmpInfo.biBitCount << endl; cout << " 压缩方式:" << BmpInfo.biCompression << endl; cout << " 实际位图数据占用的字节数:" << BmpInfo.biSizeImage << endl; cout << " X方向分辨率:" << BmpInfo.biXPelsPerMeter << endl; cout << " Y方向分辨率:" << BmpInfo.biYPelsPerMeter << endl; cout << " 使用的颜色数:" << BmpInfo.biClrUsed << endl; cout << " 重要颜色数:" << BmpInfo.biClrImportant << endl; } #endif
在twentyfourbitmap.h里,写下述代码:
#pragma once #ifndef TWENTYFOURBITMAP_H #define TWENTYDOURBITMAP_H #include <iostream> #include "BMP.h" using namespace std; class twentyfourBitMap { char imageName[30];//图像名 int width, height;//图像的宽高 BITMAPFILEHEADER bmpHead;//文件头 BITMAPINFOHEADER bmpInfo;//信息头 IMAGEDATA* imagedata = NULL, * newimagedata = NULL;//存储图片像素信息的二维数组 FILE* fpin, * fpout;//文件指针 //平滑算子也是通过模板进行处理的,所以可以把平滑处理和锐化处理通过一个函数实现 int Template1[3][3]{ 1,1,1,1,1,1,1,1,1 };//平滑模板 int Template2[3][3]{ 0,-1,0,-1,5,-1,0,-1,0 };//laplace锐化模板,4邻域 int Template3[3][3]{ -1,-1,-1,-1,9,-1,-1,-1,-1 };//laplace锐化模板,8邻域 public: bool readImage();//读取图片 bool writeImage();//保存图片 bool Operation(int x);//图像平滑和锐化处理 bool Operation(int Template[][3], int coefficient);//图像处理 bool makeGray();//将彩色图转换为灰度图 bool Binarization();//二值化 void showBmpHead(BITMAPFILEHEADER BmpHead);//显示文件头 void showBmpInfo(tagBITMAPINFOHEADER BmpInfo);//显示信息头 }; bool twentyfourBitMap::readImage() { cout << "输入要读取的图片名:"; cin >> imageName; if (!fopen_s(&fpin, imageName, "rb")) { //读取图片类型 WORD bfType; fread(&bfType, sizeof(WORD), 1, fpin);//fread()的使用 if (bfType != 0x4d42) { cout << "该图片不是BMP!" << endl; return false; } //读取文件头和信息头 fread(&bmpHead, sizeof(tagBITMAPFILEHEADER), 1, fpin); fread(&bmpInfo, sizeof(tagBITMAPINFOHEADER), 1, fpin); //检查是否是24位位图 if (bmpInfo.biBitCount != 24) { cout << "该图片不是24位!" << endl; return false; } //读取图片的像素信息 width = bmpInfo.biWidth; height = bmpInfo.biHeight; width = (width * sizeof(BYTE) + 3) / 4 * 4;//图像的每一行必须是4的整数倍 imagedata = new IMAGEDATA[width * height]; fread(imagedata, sizeof(IMAGEDATA) * width, height, fpin); //显示文件头和信息头 showBmpHead(bmpHead); showBmpInfo(bmpInfo); //关闭图片 fclose(fpin); } else { cout << "图片不存在!" << endl; return false; } return true; } bool twentyfourBitMap::writeImage() { char imageName[30]; cout << "输入处理后的位图名:"; cin >> imageName; //创建位图文件 if (fopen_s(&fpout, imageName, "wb")) { cout << "创建文件失败!" << endl; return false; } //写入位图类型 WORD bfBYTE = 0x4d42; fwrite(&bfBYTE, 1, sizeof(WORD), fpout); //写入文件头和信息头 fwrite(&bmpHead, 1, sizeof(BITMAPFILEHEADER), fpout); fwrite(&bmpInfo, 1, sizeof(BITMAPINFOHEADER), fpout); //写入图像像素数据 fwrite(newimagedata, sizeof(IMAGEDATA) * width, height, fpout); //关闭图片 fclose(fpout); //释放内存 delete imagedata; delete newimagedata; return true; } bool twentyfourBitMap::Operation(int x) { if (x == 1) return Operation(Template1, 9); else if (x == 2) return Operation(Template2, 1); else if (x == 3) return Operation(Template3, 1); else { cout << "模板调用错误!" << endl; return false; } } bool twentyfourBitMap::Operation(int Template[][3], int coefficient) { //分配新像素素组的空间 newimagedata = new IMAGEDATA[width * height]; //进行模板操作 for (int i = 0; i < height; ++i) for (int j = 0; j < width; ++j) { if (i == 0 || j == 0 || i == height - 1 || j == width - 1) *(newimagedata + i * width + j) = *(imagedata + i * width + j); else { int sum = 0; for (int m = i - 1; m < i + 2; ++m) for (int n = j - 1; n < j + 2; ++n) { sum += ((*(imagedata + m * width + n)).blue) * Template[n - j + 1][m - i + 1] / coefficient; } //8位BMP中一个像素值,对应调色板中索引号为该像素值的项所存放的RGB色彩 //所以像素值范围为0~255 //像素值小于0就取0,大于255就取255 sum = (sum >= 0) ? sum : 0; sum = (sum + ((*(imagedata + i * width + j)).blue) > 255) ? 255 : sum; //把新RGB值存入新数组 (*(newimagedata + i * width + j)).blue = sum; (*(newimagedata + i * width + j)).green = sum; (*(newimagedata + i * width + j)).red = sum; } } //保存图片 if (writeImage()) { cout << "\n处理成功!" << endl; return true; } else { cout << "\n处理失败!" << endl; return false; } } bool twentyfourBitMap::makeGray() { //分配新像素素组的空间 newimagedata = new IMAGEDATA[width * height]; //进行灰度化操作 int sum; for (int i = 0; i < width * height; ++i) { sum = (*(imagedata + i)).blue + (*(imagedata + i)).green + (*(imagedata + i)).red; (*(newimagedata + i)).blue = (*(newimagedata + i)).green = (*(newimagedata + i)).red = sum / 3; } //保存图片 if (writeImage()) { cout << "\n处理成功!" << endl; return true; } else { cout << "\n处理失败!" << endl; return false; } } bool twentyfourBitMap::Binarization() { //分配新像素素组的空间 newimagedata = new IMAGEDATA[width * height]; //二值化操作 for (int i = 0; i < width * height; ++i) if ((*(imagedata + i)).blue > 128) (*(newimagedata + i)).blue = (*(newimagedata + i)).green = (*(newimagedata + i)).red = 255; else (*(newimagedata + i)).blue = (*(newimagedata + i)).green = (*(newimagedata + i)).red = 0; //保存图片 if (writeImage()) { cout << "\n处理成功!" << endl; return true; } else { cout << "\n处理失败!" << endl; return false; } } void twentyfourBitMap::showBmpHead(BITMAPFILEHEADER BmpHead) { cout << "\n图片文件头:" << endl; cout << " 图片大小:" << BmpHead.bfSize << endl; cout << " 保留字_1:" << BmpHead.bfReserved1 << endl; cout << " 保留字_2:" << BmpHead.bfReserved2 << endl; cout << " 实际位图片数据的偏移字节数:" << BmpHead.bfOffBits << endl; } void twentyfourBitMap::showBmpInfo(tagBITMAPINFOHEADER BmpInfo) { cout << "图片信息头:" << endl; cout << " 结构体的长度:" << BmpInfo.biSize << endl; cout << " 位图宽:" << BmpInfo.biWidth << endl; cout << " 位图高:" << BmpInfo.biHeight << endl; cout << " 平面数:" << BmpInfo.biPlanes << endl; cout << " 采用颜色位数:" << BmpInfo.biBitCount << endl; cout << " 压缩方式:" << BmpInfo.biCompression << endl; cout << " 实际位图数据占用的字节数:" << BmpInfo.biSizeImage << endl; cout << " X方向分辨率:" << BmpInfo.biXPelsPerMeter << endl; cout << " Y方向分辨率:" << BmpInfo.biYPelsPerMeter << endl; cout << " 使用的颜色数:" << BmpInfo.biClrUsed << endl; cout << " 重要颜色数:" << BmpInfo.biClrImportant << endl; } #endif
在main.cpp里,写下述代码:
#include <iostream> #include "BMP.h" #include "eghtbitmap.h" #include "twentyfourbitmap.h" using namespace std; int main() { int depth = 0; cout << "输入要处理的位图深度:"; cin >> depth; if (depth == 8) { eightBitMap Image; if (Image.readImage()) { int answer; cout << "\n1.平滑处理" << "\n2.4邻域锐化" << "\n3.8邻域锐化" << "\n4.二值化" << "\n选择处理方式:"; cin >> answer; switch (answer) { case 1: Image.Operation(1); break; case 2: Image.Operation(2); break; case 3: Image.Operation(3); break; case 4: Image.Binarization(); break; default: cout << "错误选择!" << endl; } } else cout << "位图读取错误!" << endl; } else if (depth == 24) { twentyfourBitMap Image; if (Image.readImage()) { int answer; cout << "\n1.平滑处理" << "\n2.4邻域锐化" << "\n3.8邻域锐化" << "\n4.彩色位图灰度化" << "\n5.二值化" << "\n选择处理方式:"; cin >> answer; switch (answer) { case 1: Image.Operation(1); break; case 2: Image.Operation(2); break; case 3: Image.Operation(3); break; case 4: Image.makeGray(); break; case 5: Image.Binarization(); break; default: cout << "错误选择!" << endl; } } else cout << "位图读取错误!" << endl; } else cout << "错误深度输入!" << endl; cin.get(); cin.get(); }
写好代码后,编译一下。
运行
在运行前,需要先知道要操作的图像的位深度。
然后运行代码。(注生成的新的位图,需要把.bmp后缀名加上)。
示例:
最后,可以代码的文件夹中找到time1.pmg。
左原图,右8邻域处理后的图像。