圖像處理技術(RGB分離)
最近學習了圖像處理技術,第一個小工程做的事將一張圖片的rgb分離,存爲三張圖片,就像PS中的RGB通道的三張圖片一樣。
我們先準備兩張24位真彩色圖片,一張寬度像素爲4的倍數,一張則不是。
我們來看下它的文件頭和信息頭都儲存了什麼信息:
位圖文件頭BITMAPFILEHEADER,
這是一個結構,其定義如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
這個結構的長度是固定的,爲14個字節(WORD爲無符號16位整數,DWORD爲無符號32位整數),各個域的說明如下:
bfType
指定文件類型,必須是0x424D,即字符串“BM”,也就是說所有.bmp文件的頭兩個字節都是“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;
這個結構的長度是固定的,爲40個字節(LONG爲32位整數),各個域的說明如下:
biSize
指定這個結構的長度,爲40。
biWidth
指定圖象的寬度,單位是像素。
biHeight
指定圖象的高度,單位是像素。
biPlanes
必須是1,不用考慮。
biBitCount
指定表示顏色時要用到的位數,常用的值爲1(黑白二色圖), 4(16色圖), 8(256色), 24(真彩色圖)(新的.bmp格式支持32位色,這裏就不做討論了)。
biCompression
指定位圖是否壓縮,有效的值爲BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定義好的常量)。要說明的是,Windows位圖可以採用RLE4,和RLE8的壓縮格式,但用的不多。我們今後所討論的只有第一種不壓縮的情況,即biCompression爲BI_RGB的情況。
biSizeImage
指定實際的位圖數據佔用的字節數,其實也可以從以下的公式中計算出來:
biSizeImage=biWidth’ × biHeight
要注意的是:上述公式中的biWidth’必須是4的整倍數(所以不是biWidth,而是biWidth’,表示大於或等於biWidth的,最接近4的整倍數。舉個例子,如果biWidth=240,則biWidth’=240;如果biWidth=241,biWidth’=244)。
如果biCompression爲BI_RGB,則該項可能爲零
biXPelsPerMeter
指定目標設備的水平分辨率,單位是每米的象素個數,關於分辨率的概念。
biYPelsPerMeter
指定目標設備的垂直分辨率,單位同上。
biClrUsed
指定本圖象實際用到的顏色數,如果該值爲零,則用到的顏色數爲2biBitCount。
biClrImportant
指定本圖象中重要的顏色數,如果該值爲零,則認爲所有的顏色都是重要的。
一開始我想將原圖的每個像素點取出來,然後將RGB分量取出來分別賦值,這樣處理對於寬度爲4的倍數的圖片是沒有問題的。但是到了不是4倍數的圖片就出問題了。
後來瞭解到圖片的讀取和儲存都是按字節來進行,而且是必須要是4 的倍數,那我們的解決方案就明確了,把所有字節讀出來後再賦值。
那麼我們怎麼樣讀出所有的字節來呢?
if (bmpWidth % 4 != 0) {
bmpWidth = (bmpWidth * infoHeader.biBitCount / 8 + 3) / 4 * 4;
}
else {
bmpWidth = bmpWidth * infoHeader.biBitCount / 8;
}
利用這個公式轉換寬度,再乘以高度就是所有字節的數量了。
下面附上代碼:
#pragma once
#include<iostream>
#include<fstream>
#include<Windows.h>
using namespace std;
bool readBmp(char *bmpName) {
FILE *fb = fopen(bmpName, "rb");
FILE* pfoutr = fopen("r.bmp", "wb");
FILE* pfoutg = fopen("g.bmp", "wb");
FILE* pfoutb = fopen("b.bmp", "wb");
if (fb == 0) {
return 0;
}
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
int bmpWidth =0;//圖像的寬
int bmpHeight=0;//圖像的高
int bmpOffset = 0;
fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fb);
fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fb);
bmpHeight = infoHeader.biHeight;
bmpWidth = infoHeader.biWidth;
bmpOffset = fileHeader.bfOffBits;
if (bmpWidth % 4 != 0) {
bmpWidth = (bmpWidth * infoHeader.biBitCount / 8 + 3) / 4 * 4;
}
else {
bmpWidth = bmpWidth * infoHeader.biBitCount / 8;
}
if (infoHeader.biBitCount >= 1) {
int size1 = bmpHeight * bmpWidth;
BYTE *img = new BYTE[size1];
BYTE *img1 = new BYTE[size1];
BYTE *img2 = new BYTE[size1];
BYTE *img3 = new BYTE[size1];
fseek(fb, bmpOffset, 0);
fread(img, sizeof(BYTE), size1, fb);
for (int i = 0;i < bmpHeight ;i++) {
for (int j = 0;j < bmpWidth;j++) {
switch (j % 3) {
case 0:
img1[i*bmpWidth + j] = 0;
img2[i*bmpWidth + j] = 0;
img3[i*bmpWidth + j] = img[i*bmpWidth + j];
break;
case 1:
img1[i*bmpWidth + j] = 0;
img2[i*bmpWidth + j] = img[i*bmpWidth + j];
img3[i*bmpWidth + j] = 0;
break;
case 2:
img1[i*bmpWidth + j] = img[i*bmpWidth + j];
img2[i*bmpWidth + j] = 0;
img3[i*bmpWidth + j] = 0;
break;
}
}
}
fwrite(&fileHeader, sizeof(fileHeader), 1, pfoutr);
fwrite(&infoHeader, sizeof(infoHeader), 1, pfoutr);
fwrite(img1, sizeof(BYTE), size1, pfoutr);
fwrite(&fileHeader, sizeof(fileHeader), 1, pfoutg);
fwrite(&infoHeader, sizeof(infoHeader), 1, pfoutg);
fwrite(img2, sizeof(BYTE), size1, pfoutg);
fwrite(&fileHeader, sizeof(fileHeader), 1, pfoutb);
fwrite(&infoHeader, sizeof(infoHeader), 1, pfoutb);
fwrite(img3, sizeof(BYTE), size1, pfoutb);
}
fclose(fb);
fclose(pfoutr);
fclose(pfoutg);
fclose(pfoutb);
return 1;
}
int main() {
char bmpName[] = "sea.bmp";
readBmp(bmpName);
}
這代碼我就是測試用,寫代碼時要注意規範,提取函數哈!
原圖:
R通道:
G通道: