一個(gè)菜鳥(niǎo)的圖像處理入門(mén)
本來(lái)就是入門(mén)的 那就先說(shuō)下gdi 跟 bmp 這些東西吧。
本文引用地址:http://dyxdggzs.com/article/201704/346204.htm1 gdi跟bmp
vc里的CDC 也就是設備上下文 相當于c#里的graphics ,也有lineTo等方法。
其實(shí)我們在c#中使用graphics的時(shí)候就已經(jīng)在使用gdi+了我們卻渾然不覺(jué)
那么gdi到底在哪里呢 試著(zhù)在c盤(pán)搜索gdiplus或者gdi32名字的文件 你應該會(huì )找到 就像這個(gè)

直接刪除應該刪不掉 不過(guò)你可以給他改個(gè)名字 別改了自己都搞忘了O(∩_∩)O哈!。
然后你隨便運行個(gè)程序比如QQ 腫么樣
Initialization failure:0x0000000E
使用windows自帶的圖片查看器
加載 c:windowssystem32shimgvw.dll時(shí)出錯 系統找不到指定的文件。
所以說(shuō)windows下到處都是gdi
不要以為bitmap是一種圖像格式 就像jpg gif 一樣 實(shí)際上他們是兩個(gè)完全不同的概念。
在vc++里叫cbitmap 也就是對應的gdi數據模型 等同于c#里的bitmap
可以這樣說(shuō).bmp的位圖文件是gdi的文件表現形式。 位圖文件不進(jìn)行圖像壓縮算法操作直接存儲像素矩陣信息所以文件體積非常大
jpg文件體積非常小為什么 jpg實(shí)際上它是按照完全不同的算法跟理念來(lái)存儲圖像的 都知道人的視覺(jué)效應
主要體現在兩個(gè)方面 色彩的明暗度 色彩的飽和度 也就是色調(說(shuō)俗點(diǎn)就是赤橙黃綠青藍紫 )并且肉眼根本達不到每個(gè)像素那么大的分辨能力
jpg就是按照這種方式來(lái)存儲的
2 位圖文件格式
那么就先說(shuō)下bmp文件格式吧,本人不是那種長(cháng)篇大論型的 不說(shuō)廢話(huà)。
這個(gè)壓縮包里工程的bin目錄有一個(gè)叫bmpTestImg.bmp兩色的位圖文件。以16進(jìn)制編輯器打開(kāi)對照下圖看 :
這里是下載鏈接

vc++里有定義好的bitmapheader 用來(lái)表示位圖頭信息 在C#里沒(méi)有。
其實(shí)沒(méi)多大關(guān)系的這只是一種數據組織方式
如果你愿意也可以定義這么一個(gè)結構。
gdi與設備無(wú)關(guān) 但是他并不代表跟設備沒(méi)有任何關(guān)聯(lián) 計算機之間傳遞的是圖片文件或者圖像數據 并不是gdi對象。
微軟幫我們搞了這一層東西,就是說(shuō)只要是接入windows的設備我們都可以通過(guò)
gdi在那個(gè)設備上顯示 輸出東西 而不用關(guān)系設備本身 ,可以說(shuō)整個(gè)windows提供給我們的就是gdi 所有窗體
等等都是gdi繪制的,比如說(shuō)做啥xx編程的時(shí)候要直接操縱顯卡 實(shí)際上直接操縱的方式速度更快但是沒(méi)有必要
所謂的24位真彩色 mspaint畫(huà)圖新建圖像默認存儲 就是24位真彩色 這并不是什么高深技術(shù)因為 一個(gè)像素的顏色用3個(gè)八位
來(lái)表示就是24位真彩色
真彩圖像是說(shuō)他具有顯示 256x256x256種顏色的能力
還有就是c#里默認新建的bitmap對象就是24位真彩的 并且graphic提供的函數不能操作非真彩色的位圖
3 水平不咋滴,還是來(lái)敲點(diǎn)代碼吧,(^o^)/~
先賣(mài)個(gè)關(guān)子哈 上面你下載的示例圖片你看到東西了嗎 還是黑乎乎一片 嘿嘿。如果你看到了那你見(jiàn)鬼了 還是趕快拜拜春哥吧
最近在研究那啥dicom 也學(xué)會(huì )拽文了 嘿嘿
是否可以這么描述:bmp是一種約定俗成的有規律的數據組織方式 不論他在內存中 在文件中 他跟特定編程語(yǔ)言無(wú)關(guān) 跟平臺無(wú)關(guān)
bmp格式簡(jiǎn)而言之一句話(huà) 前54字節存儲文件頭信息最主要就是圖像位數跟寬度高度,從54位開(kāi)始有調色板則是調色板信息 無(wú)調色板則是像素數據。
由于本文不是專(zhuān)門(mén)探討bmp文件格式 詳細請參見(jiàn)bmp格式
好下面我們就來(lái)讀取這種有規律的數據: 寫(xiě)第一個(gè)按鈕事件的代碼
void bmpRead()//讀取bmp文件格式
{
Image bmp = (Bitmap)Image.FromFile("bmpTestImg.bmp");
MemoryStream bmpData = new MemoryStream();
bmp.Save(bmpData, ImageFormat.Bmp);
BinaryReader br = new BinaryReader(bmpData);
//為什么要偏移18個(gè)字節 因為bmp格式"龜腚"在18字節那個(gè)地方開(kāi)始用32位整型存儲圖像的寬度跟高度
bmpData.Seek(18, SeekOrigin.Begin);
int width = br.ReadInt32();
int height = br.ReadInt32();
MessageBox.Show(string.Format("寬{0},高{1}", width, height));
//第11個(gè)字節處儲存數據字節的起始位置
bmpData.Seek(10, SeekOrigin.Begin);
int dataStart = br.ReadInt32();
byte[] datas = new byte[width * height];
int indx = 0;
bmpData.Seek(dataStart, SeekOrigin.Begin);
//注意咯 這是調色板開(kāi)始的位置 更改調色板將會(huì )讓"看不見(jiàn)"的圖像顯示出來(lái)
bmpData.Seek(54, SeekOrigin.Begin);
Random rd = new Random();
bmpData.Write(new byte[] { (byte)rd.Next(0, 255), (byte)rd.Next(0, 255),
(byte)rd.Next(0, 255), 0 }, 0, 4);
bmpData.Write(new byte[] { (byte)rd.Next(0, 255), (byte)rd.Next(0, 255),
(byte)rd.Next(0, 255), 0 }, 0, 3);
Image newbmp = Bitmap.FromStream(bmpData);
Graphics.FromHwnd(this.Handle).DrawImage(newbmp, new Point(0, 0));
bmpData.Close();
br.Close();
}
上面的代碼很簡(jiǎn)單滴 (⊙o⊙)哦 都看得懂吧 別忘了要在執行文件同級目錄放上偶的圖片哦 嘿嘿。
這個(gè)適合用來(lái)給girlfriend表白啊啥的O(∩_∩)O哈!
有幾個(gè)需要說(shuō)明的地方
bmp文件的兩色 并非一定得是黑白 對吧 可以是紅色綠色, 也可以是兩種相同的色兒 對吧
為什么寬度要在第19字節的位置開(kāi)始存儲 沒(méi)有為什么 這是bmp格式的“龜腚”對吧 要問(wèn)去問(wèn)蓋茨大叔
對于“流”的操作 seek到前面去了 再進(jìn)行write操作 是否就把對應位置的數據“擠”到后面去了呢?
NO 數據流是一種游標 “覆蓋”型的操作 長(cháng)度會(huì )自動(dòng)標識到游標到過(guò)最遠的地方 文件流內存流都一樣
所以說(shuō)想要做數據插入啊 文件合并啊之類(lèi)的東東的話(huà)得弄兩個(gè)數據流對象哈 互相倒騰數據 這樣才能達到目的。
又扯遠了哈 打住。
不是說(shuō)讀取數據嗎 就是讀取像素值數據啊,現在開(kāi)始
既然是讀取像素值,咱得一行一行的讀啊 就像掃描一樣的。實(shí)際上他就是以這種方式存儲的哈 只不過(guò)稍微有點(diǎn)不一樣
那就是圖像數據每行以四倍字節為基數不足以0補齊 乃明白了木有 。
比如說(shuō)這一個(gè)掃描行有3個(gè)像素 那么就是9字節 ,4字節的倍數那么他必須要有12字節 那么剩下的3字節全是0。
比如說(shuō)這一個(gè)掃描行有20個(gè)像素 那么就是60字節 ,4字節的倍數那么他必須要有60字節 因為60/4正好除凈。
先來(lái)說(shuō)下這個(gè)破公式 ((width * 24 + 31) / 32 * 4) 不知道是哪個(gè)頭腦發(fā)熱的人想出來(lái)的 ,注意這里的32是指32位 即4字節。
實(shí)際上我只想說(shuō)兩個(gè)字非常扯淡 一定是很深入的掌握了數據長(cháng)度運算的本質(zhì), 一句把我上面那n多句都代替了
width是圖像寬度 24代表每個(gè)像素位數。 計算出實(shí)際字節數 先假設他會(huì )超出一位 補齊31位 然后通過(guò)整型數據相除的性質(zhì) 除以4字節得到4字節的倍數
注意最終得到的是掃描行的字節數
就這樣從圖像左下角第一個(gè)點(diǎn)開(kāi)始 一行一行從左至右的往上掃描
然后是bmp圖像素的存儲方式是BGR的順序哈 而不是通常的RGB 哦 別搞錯了,
以前很菜的時(shí)候用SetPixel()處理像素 被人罵慘了 現在俺依然來(lái)寫(xiě)個(gè)setPix() 嘿嘿 第二個(gè)按鈕的代碼:
void setPix()//
{
FileStream bmpData = File.Open("mm.bmp", FileMode.Open); BinaryReader br = new BinaryReader(bmpData);
bmpData.Seek(10, SeekOrigin.Begin);int bmpDataStart = br.ReadInt32();
bmpData.Seek(18, SeekOrigin.Begin);int width = br.ReadInt32();int height = br.ReadInt32();
Bitmap newBmp = (Bitmap)new Bitmap(width, height, PixelFormat.Format24bppRgb);
MemoryStream newBmpData = new MemoryStream();
newBmp.Save(newBmpData, ImageFormat.Bmp);BinaryReader br2 = new BinaryReader(newBmpData);
newBmpData.Seek(10, SeekOrigin.Begin); int newBmpDataStart = br2.ReadInt32();
newBmpData.Seek(newBmpDataStart, SeekOrigin.Begin);
for (int i = 0; i < height; i++)
{
bmpData.Seek(((width * 24 + 31) / 32 * 4) * i + bmpDataStart, SeekOrigin.Begin);
newBmpData.Seek(((width * 24 + 31) / 32 * 4) * i + newBmpDataStart, SeekOrigin.Begin);
for (int j = 0; j < width; j++)
{
//注意bmp的像素值是按照bgr的順序存儲的哦
byte[] data = new byte[3];
bmpData.Read(data, 0, 3);
newBmpData.Write(new byte[] { data[2], data[1], data[0] }, 0, 3);
}
//下面的填充值要不要都可以
int fill = ((width * 24 + 31) / 32 * 4) - width * 3;
if (fill > 0)
{
byte[] fills = new byte[] { 0, 0, 0 };
newBmpData.Write(fills, 0, fills.Length);
}
}
newBmpData.Flush();
newBmp = (Bitmap)Bitmap.FromStream(newBmpData);
Graphics.FromHwnd(this.Handle).DrawImage(newBmp, new Point(0, 0));
bmpData.Close(); newBmpData.Close();
br.Close(); br2.Close();
}
如果你把for (int i = 0; i < height; i++)改成 for (int i = 0; i < height/2; i++) 可以看下效果 可以證明在文件中是按照圖像從左至右往上 的方式存儲的
通過(guò)以上可以看出任何環(huán)境下他都是按照同種規律存儲存儲的,就像dicom 只要遵循這種規律就能通過(guò)這種格式實(shí)現數據共享。
都說(shuō)lockBitmap的方式是最快的 ,確實(shí)是最快的哈 因為他是使用指針的方式
下面是把一個(gè)圖像轉成灰度圖 你看 不但代碼少了很多 并且還不用費盡心思去確定每一個(gè)掃描行的索引 你看 刷的一下 就出來(lái)了 嘿嘿
注意有unsafe代碼 在項目->屬性 勾選“允許不安全代碼” :
void lockPix()
{
Bitmap bmp = (Bitmap)Image.FromFile("mm.bmp");
BitmapData datas = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
unsafe
{
byte* p = (byte*)datas.Scan0;
int indx = 0;
for (int i = 0; i < bmp.Height/2; i++)
{
for (int j = 0; j < bmp.Width; j++)
{
byte b, g, r; b = p[indx + 1]; g = p[indx + 2]; r = p[indx + 3];
//byte lightLv = (byte)(r * 0.3 + g * 0.59 + b * 0.11);
byte gray = (byte)((r + g + b) / 3);
p[indx++] = gray; p[indx++] = gray; p[indx++] = gray;
}
}
}
bmp.UnlockBits(datas);
Graphics.FromHwnd(this.Handle).DrawImage(bmp, new Point(0, 0));
bmp.Dispose();
}
灰度圖 哎呀 跟你說(shuō)得又俗又土點(diǎn)就是對每個(gè)像素 rgb三個(gè)值加起來(lái)除以3 不想跟你講那些我自己都不怎么明白的東西
但是還是不得不跟你說(shuō)下所謂的yuv表示方式 y代表明度 說(shuō)到這個(gè)又得要講下矩陣乘法 真麻煩ya。

這個(gè)什么意思呢,先說(shuō)說(shuō)矩陣乘法吧
比如你商店里有帽子鞋子 襪子 單價(jià)分別表示為:
[25] [80] [15]
然后今天帽子賣(mài)了3件 鞋子賣(mài)了1件 襪子賣(mài)了兩件,可表示為:
[3]
[1]
[2]
然后今天的收入呢=25x3+80x1+15x2 總共185 用矩陣表示為[185]
我第二天帽子賣(mài)了1件 鞋子賣(mài)了兩件 襪子賣(mài)了3件,那么這兩天的銷(xiāo)售可表示為:
[3] [1]
[1] [2]
[2] [3]
那么這兩天總共的收入呢=(25x3+80x1+15x2)+(25x1+80x2+15x3) 總共185+230=415 用矩陣表示為[185][230]
沒(méi)錯 你看到的這就是矩陣乘法 不想講什么線(xiàn)性代數 什么的那么高深的理論
比如上面RGB轉轉YUV公式的3行3列 乘 3行1列 乘出來(lái)是 3行1列 ,
規律就是第一個(gè)矩陣的列跟第二個(gè)矩陣的行一致,得到一個(gè)首行尾列數的二維矩陣。
注意一定是得到一個(gè)首行尾列數的二維矩陣,如果不符合這個(gè)標準 那么運算是無(wú)意義的。
運算規則:要從結果矩陣的往前推,先確定結果矩陣元素所在行數列數 C(i,j)。然后把A矩陣對應行 與B矩陣對應列
相同索引位置的數兩兩相乘 然后加起來(lái) 即為C(i,j)的值。 其實(shí)還是挺簡(jiǎn)單的。
這里有關(guān)于矩陣乘法 運算規則的簡(jiǎn)介:http://www2.edu-edu.com.cn/lesson_crs78/self/j_0022/soft/ch0605.html
如果rgb值分別是{115,20,65} 那么轉換成yuv表示應該是
y=115x0.299+20x0.587+65x0.114
u=115x-0.148+20x-0.289+65x0.437
v=115x0.615+20x-0.515+65x-0.1
貌似很難理解 因為這個(gè)跟前面那個(gè)賣(mài)東西的又不一樣了可以換個(gè)角度看 。
把第二個(gè)矩陣往左“倒下來(lái)” 就是說(shuō)讓他的行跟第一個(gè)矩陣的列 對齊 是不是感覺(jué)好多了O(∩_∩)O哈!
整點(diǎn)復雜的 那再來(lái)隨便整個(gè)吧
[2] [4] [-1] [6]
[1] [0] [3 ] [5]
結果是多少
2x-1+4x3 2x6+4x5
1x-1+0x3 1x6+0x5
最終結果
[10] [32]
[-1] [6]
也可以把它分解為單行單列的來(lái)看 就簡(jiǎn)單多了哈
有種很特殊的矩陣 有點(diǎn)像對角線(xiàn)
任何跟他相乘的矩陣都等于那個(gè)矩陣本身 有點(diǎn)像 “任何數乘以1 都等于那個(gè)數本身”
[1][0]
[0][1]
看吧矩陣乘法就是這么神奇的東東,通過(guò)矩陣乘法還可以進(jìn)行角度旋轉 縮放等等
這個(gè)是很高深的研究課題了O(∩_∩)O哈! 這里就不討論了
終于說(shuō)完了 俺喝口水了先。也不知講清楚了沒(méi) 下面是各種矩陣乘法的示例代碼 關(guān)于為什么是5x5的矩陣這個(gè)可以看下msdn:
知識學(xué)無(wú)止境
第三個(gè)按鈕:
void matrixColor()
{
Bitmap bmp = (Bitmap)Image.FromFile("mm.bmp");
ImageAttributes ia = new ImageAttributes();
//灰階
//float[][] colorMatrix ={
// new float[]{0.299f,0.299f, 0.299f, 0, 0},
// new float[]{0.587f,0.587f, 0.587f, 0, 0},
// new float[]{0.114f,0.114f, 0.114f, 0, 0},
// new float[]{0, 0, 0, 1, 0},
// new float[]{0, 0, 0, 0, 1}
// };
//灰階
//float[][] colorMatrix ={
// new float[]{0.3f, 0.3f, 0.3f, 0, 0},
// new float[]{0.3f, 0.3f, 0.3f, 0, 0},
// new float[]{0.3f, 0.3f, 0.3f, 0, 0},
// new float[]{0, 0, 0, 1, 0},
// new float[]{0, 0, 0, 0, 1}
// };
//反色
//float[][] colorMatrix ={
// new float[]{-1, 0, 0, 0, 0},
// new float[]{0, -1, 0, 0, 0},
// new float[]{0, 0, -1, 0, 0},
// new float[]{0, 0, 0, 1, 0},
// new float[]{1, 1, 1, 0, 1}
// };
//亮度
float[][] colorMatrix ={
new float[]{1, 0, 0, 0, 0},
new float[]{0, 1, 0, 0, 0},
new float[]{0, 0, 1, 0, 0},
new float[]{0, 0, 0, 1, 0},
new float[]{l, l, l, 0, 1}};
l -= 0.1f;
ColorMatrix cm = new ColorMatrix(colorMatrix);
ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
Graphics.FromHwnd(this.Handle).DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height),
0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, ia);
}
float l = 0.5f;
rgb為3個(gè)能量值 我們看到屏幕上花花綠綠 顏色是因為三個(gè)能量值產(chǎn)生差異化 說(shuō)俗點(diǎn)就是三個(gè)值的比例不一樣
如果三個(gè)值一樣的話(huà)那就跟電燈泡無(wú)異了 就是純亮度表示 即我們常說(shuō)的灰度圖。
來(lái)寫(xiě)個(gè)手工滴 很山寨滴 效率很低滴 更改亮度的函數
void light(ref int r, ref int g, ref int b)
{
//計算后的平均值
//增加亮度
float gray= (r + g + b) + level * 90 > 255 * 3 ? 255 * 3 : (r + g + b) + level * 90;
//降低亮度
//float gray = (r + g + b) - level * 90 < 0 ? 0 : (r + g + b) - level * 90; ;
float percentR = (float)r / (r + g + b), percentG = (float)g / (r + g + b), percentB = (float)b / (r + g + b);
r = (int)(gray * percentR > 255 ? 255 : gray * percentR);
g = (int)(gray * percentG > 255 ? 255 : gray * percentG);
b = (int)(gray * percentB > 255 ? 255 : gray * percentB);
float ren = gray - (r + g + b);
if (ren >= 3)
{
r = (r + (int)ren) > 255 ? 255 : (r + (int)ren);
g = (g + (int)ren) > 255 ? 255 : (g + (int)ren);
b = (b + (int)ren) > 255 ? 255 : (b + (int)ren);
}
}
int level = 0;
其實(shí)呢也遠可以不必這樣 直接rgb分別乘以1.2 或者1.1之類(lèi)的就可以了 只不過(guò)顏色會(huì )失真
好了終于寫(xiě)完啦 好累ya
完了 ( ⊙ o ⊙ ) 本來(lái)就很菜 這點(diǎn)破秘密全被你們曉得了 以后出去俺還雜混吶
當然作為一個(gè)商業(yè)化的軟件 代碼的容錯也是很重要的 你看acdsee 你把文件數據部分刪除一些他照樣能夠顯示 當然這些都是很簡(jiǎn)單的哈。
評論