VC+DirectShow對視頻進行圖片處理之四

來源:互聯網
上載者:User
影像處理

  在我的程式中影像處理函數是作為 DirectShow 封裝類一部分的,我認為這樣便於移動和使用。沒有連著上面的 DirectShow 類而另外寫標題是因為我覺得有必要把它提到重要位置。在程式完善階段您的工作基本就在這裡了,除了花心思構造D3D環境外幾乎所有效果都要在這裡實現,水平高下也體現於此。

  在網上可以找到很多映像特效的代碼和解說,我結合編程過程再說說。

  1. 訪問緩衝區的麻煩。

  這是最麻煩的,二維映像在這裡以一個連續的一維緩衝區呈現,您要靠一個指標去訪問它,怎麼辦呢?先弄懂 Pitch,例如 32bit ARGB 映像,每個象素就佔用 4 Byte 記憶體空間(1 Byte = 8 bit),對於寬度為 20 像素的映像,它的 Pitch 就是 80 Byte,即每一行佔用的記憶體。按 x 、y 座標就有如下公式(按 Byte 計算):

B:y * Pitch + x * 4
G:y * Pitch + x * 4 + 1
R:y * Pitch + x * 4 + 2
A:y * Pitch + x * 4 + 3

  可以看出在記憶體中是按 BGRA 儲存的,我不明白為什麼這樣,可能可以從電腦的記憶體儲存方式找到答案。上述公式計算多,效率較低,在實際使用中應適時作有益的改變。

  2. 浮雕。

  到目前為止我在網上找到的幾篇文章都說把一點的值減去其右下角點的值再加上128就行了。為什麼要減去右下角的點呢?為什麼要加上128呢?原來浮雕是要把映像的變化部分反白出來,而把相同部分淡化,所以用一點減去其鄰域任意點都可以達到這個目的,倒也不一定是右下角的點,包圍著它的八個點都可以,甚至可以選擇減去更遠的點,只要規則明確、效果好就行。在相減後點的 RGB 值都減小了,大多接近黑色,黑乎乎一片的看不出什麼來,一點也不像浮雕,所以要給它們都增加一個相同的亮度,通常加上128,其它的值,例如64、100,當然也行,一切都以實際效果為準。說到效果,上面所說的RGB相減會造成浮雕有一些色點,解決方案是計算兩點的亮度之差,RGB都賦值為亮度差,畫面就沒色點了,因為已經變成灰階圖了。亮度公式是 Brightness = 0.3 * R + 0.6 * G + 0.1 * B,其中G 的比重最大,可以近似的用 G 作為亮度,在RGB各自的分量圖中也可以明顯看出 G 分量的圖最亮,簡單的把 G 的值賦給 R 和 B 就得到灰階圖了,這可以減少計算,提高速度。後來我還看到這樣的話句,"用3 * 3 的小塊做的浮雕效果更好",不過我不知道怎麼用,可能這樣就可以實現 PhotoShop 那樣更好的浮雕效果。

  原理是這樣了,到了編程卻是另外一回事:能夠把規則、數學公式轉換為程式也是能力的一種體現。如果要減去右下角的點,那麼最右一列和最後一行是要特殊處理的,否則肯定會發生記憶體訪問錯誤,想一想就知道為什麼;如果要減去左邊的點,第一列也要特殊處理,請問第一列的點到哪裡找它左邊的點呢?不要小視此問題,它會令你訪問記憶體時遇到一些問題。

  3. 鉛筆畫

  鉛筆畫原理和浮雕差不多,也是亮度相減,認為變化大的是邊緣,然後設定一個閥值,例如差值大於8,則把該點設為黑色(0,0,0),要不設為白色(255,255,255)。閥值、色彩都可任意設定,沒人要您拘束就不要忸忸怩怩的不敢改動。

  按照此方法得到的效果實在不怎麼樣,可惜我不是研究映像的料,對資料的處理能力很差,同樣一幅在專家手中可以玩出很多花樣的圖片,淪落到我手上也只能飲恨屈膝投降無奈了。這是我看了一些影像處理方面資料和書籍所發的呆歎,影像處理實在太精深了,既要數學、物理知識雄厚,又要腦子靈活能東移西就把各種知識綜合運用,不然就只好望洋興歎。

  4. 加亮、對比等

  首先悲痛的說明,我曾努力的要實現色度、飽和度的調整,知道是要把 RGB 轉換成 HLS 之類的色彩空間才能實現,也找到了一些它們之間轉換的說明和轉換函式,可惜看不明白,或者說那些材料根本不打算讓我明白!這不單是氣話,而且事實,我真的十分氣憤:怎麼能夠在前面鋪了一大堆""說了一大堆廢話然後給個只有幾行無大用的注釋的代碼就可以呢?!儘管如此憤概,我還是乖乖的抄了程式,希冀能發揮作用,結果卻是失望:不僅效率低下,而且在調整了飽和度的同時使映像出現不協調的彩色方塊。由於不知道原理,無法改動,於是我放棄了它。

  調整亮度很簡單,例如要加亮 10,把 RGB 都加上 10 就可以了,減亮就減10。

  對比調整也不難,書上說是要令亮點更亮、暗點更暗,好像是要找出亮點來增亮、找出暗點加暗,其實不然,把所有點都乘以一個數,把亮暗點的差距拉大或減小就能調整對比了。把映像原來的對比定為 1,要增大對比就調整為 1.3 、2 等大於1 的數,把每個點的 RGB 都乘以它,就行了,要降低就把數值設為 0 至 1 的數。只要注意保持 RGB 的值在 0 ~ 255 中即可。
5. 馬賽克

  馬賽克效果就是把映像分解成 m * n 個小塊 或長寬為 x 、y 的小塊,用小塊內的某點顏色作為整塊的顏色,通常用左上方的顏色。

  動起手編程會很麻煩,要定位到每小塊的左上方,才能改變塊內的顏色,因此要用很多迴圈,我在代碼中就用了12個迴圈!除此,還有邏輯麻煩,拿分成寬高為 x、y 的小塊這種情況為例,您不能保證映像的長寬剛好都是 x、y的倍數,很多時會餘出一些"邊角料",這就是麻煩,不可能捨棄它們不進行處理,因為很影響效果。因此,要先處理藍色的倍數部分,再處理綠色的寬度上餘下部分,處理紅色的高度上餘下部分,還有黃色的寬高夾縫的小塊。

  除了這種長方形的處理,還可以實驗上面右圖菱形等形狀,當然,您要付出很大的勞動,而我沒能做到這些。

  6. MMX

  記得在上面我說過會在文章中涉及一點MMX,不妨在這裡涉及。在 VC 中可以鑲入彙編使彙編變得很容易,完全不是純彙編代碼所能相比的,所以不用怕彙編,可以先用 C 語言寫出實現代碼,再用彙編"翻譯"過來。如果譯不出來,更加可以把代碼中斷一下,讓 VC 反組譯碼,看 VC 的彙編代碼,再行改進,為什麼不行呢,有人用槍指著您嗎?記住哦,如果沒辦法改進就放棄彙編,不要做多餘的事。其實要用 MMX 也不一定非用彙編不可,VC 也提供了 MMX 的 C++ 封裝,學習後可用它,我則懶於學習。

  MMX 最大的好處是可以自動保證處理的值範圍為0 ~ 255,節省判斷,而且MMX寄存器是64位的,一次可處理32bit映像的兩個點。其它的我也不太懂,您可參考相關資料。

  下面列出浮雕效果代碼,它是減去右邊點的,由於不進行行列判斷,每行最後一點減去的是下一行的首點。

__int64 Mask = 0x8080808080808080; // 0x80 = 128,就是亮度的增加值
UINT a = bmpBufferLen >> 3; // 緩衝區長度(按 BYTE 計算)除以 8(兩個點的大小),計算要迴圈的次數
_asm{
mov esi,pIn; // 要處理的緩衝區指標
mov edi,pOut; // 峁撼邇剛?BR>mov eax,a; // 迴圈次數
dec eax; // 迴圈次數減一,因為最後兩點沒法減,可以在後面特殊處理,這裡不作處理
movq mm1,Mask; // 增加值,movq 是 MMX 的專用彙編指令,請找資料看
_loop: // 迴圈
mov ecx,esi; // ecx 儲存右邊點的指標
add ecx,4; // 只加 4 就跳過一點到右邊點了
movq mm0,[esi]; // 移動要處理的兩點的值到 MMX 寄存器
movq mm2,[ecx]; // 移右邊兩點的值
psubusb mm0,mm2; // 相減
paddusb mm0,mm1; // 加上增加值
movq [edi],mm0; // 移到結果緩衝區
add esi,8; // 移動到下兩點
add edi,8; // 同上
dec eax; // 迴圈計數減一
jnz _loop; // 不為零就繼續迴圈
emms; // 結束 MMX 使用
}

 7. 來點進階的,用網路攝影機控制滑鼠!

  看著這個有點神奇吧,其實比什麼都要簡單。先做好"硬體準備":把網路攝影機擺放,鏡頭下方放張白紙以使映像中物體界限分明。

  軟體方面,把圖片作閥值處理-- B 值大於 128 的設為黑色,其它的設為白色。因為白紙的作用,您的手或其他物體會在閥值圖中顯示為白色,,再找出圖中紅點,即第一點白點的在圖中位置(x,y),再把圖的座標影射到螢幕座標就行了。

 

  下面要討論具體做法。先解決映像座標問題。我擷取第一點白點的程式如下:

void CDSControl::GetMousePos(BYTE* pb,int *xPos,int* yPos)
{
 int x,y;
 BOOL mouseFound = FALSE;
 for(y = 0; y < m_bmpHei; y ++){
  for(x = 0; x < m_bmpWid; x ++){
   if(pb[0] == 255){
    // 因為白色為(255,255,255),判斷一個255 即可
    pb[2] = 255; // 設為紅色,別忘了 BGRA 的記憶體相片順序
    pb[1] = pb[0] = 0; //
    // 計算座標
    *xPos = ScreenWid - x * ScreenWid / m_bmpWid;
    *yPos = y * ScreenHei / m_bmpHei;
    mouseFound = TRUE;
    break;
   }
   pb += 4;
  }
  if(mouseFound){
   break;
  }
 }
}

  可以看出我是從所得的映像緩衝區的第一點開始檢索的,這也是網路攝影機掃描 CCD 的順序,看上面右邊的圖,那是右手的映像,可以推想出網路攝影機的掃描順序如所示:

 
(網路攝影機掃描座標) (與螢幕座標(藍)相對的網路攝影機座標(紅))

  而相對於螢幕座標很容易得到上面右圖,說明螢幕座標以左上方為原點,x的正方向為右,而網路攝影機座標以右上方為原點,x的正方向為左,也就是說我按順序尋找所得到的座標值是這樣的:y 與螢幕座標 y 相符,x 則與螢幕座標 x 剛好相反。因此推算出滑鼠位置應該是:

*xPos = ScreenWid - x * ScreenWid / m_bmpWid;
*yPos = y * ScreenHei / m_bmpHei;

  其中 ScreenWid 、ScreenHei 分別是螢幕的寬和高,用 ScreenWid = GetSystemMetrics(SM_CXSCREEN) 和 ScreenHei = GetSystemMetrics(SM_CYSCREEN) 得到,而m_bmpWid、m_bmpHei 當然是圖片的寬和高了。得到滑鼠座標後再用 SetCursorPos 設定即可,只不過滑鼠晃動會比較厲害,這與映像噪音有關,可能先做個柔化處理會好一點,但鑒於網路攝影機的攝像品質,我不想作無謂的掙紮,您不會真的想用網路攝影機代替滑鼠吧?!

  在使用前應該調整二值圖的閥值,使整個圖都變成黑色,保證能正確濾除幹擾,不然在按下"滑鼠控制"按鈕後您的滑鼠就不會聽話,您會無法控制好它。請問沒有滑鼠的幫忙您將如何關閉程式?對了,"Alt + F4",別忘了,否則您得硬著動手把網路攝影機拔掉!!

  可以說控制滑鼠真的很容易實現,不過效果出奇的不錯,這種好像無影無蹤的控制方式相當令人驚奇,記得我的大哥看程式時對我前面的影像處理沒有一絲反應,看到這個卻大大的驚奇!呵呵。如果您有興趣的話可以在此方面做更多的實驗,例如可以把手裁剪出來,讓它參加撥動一個小球等遊戲,只要您的幾何過關、有毅力就可以實現。

  8. 更實用的數字減影技術

  請看上面兩幅圖,左邊的是先儲存一幅背景圖然後把手放到網路攝影機前攝像,用攝到的圖片減去背景圖得到的圖;中圖是根據左圖把手的顏色設為原來顏色得到的;右圖是不斷用新圖片減去上一幅圖得到的手移動痕迹圖。這充分顯示了數字減影的功用:能夠從背景分解物體和偵測到物體的運動。

  如此說來此技術在安保方面的應用會很突出,像上面右圖那樣不斷減去上一幅圖片,當減影后得到的圖片差別大於某一程度的點多到一定數值時就說明有情況發生,這時候就提醒保安工作,彌補保安的人為失誤,也可以在此時啟動錄影錄取有價值的情況。請看程式:

void CDSControl::DNS(BYTE* pIn,BYTE* pReduce,BYTE* pOut)
{
 // pIn 新圖的資料區指標,pReduce 背景指標,pOut 儲存區指標
 if(!pReduce) return; // 沒有背景圖就不處理
 int differentPoint = 0;
 for(int i = 0; i < m_bmpBufferLen; i ++){
  // m_bmpBufferLen 為資料區長度
  pOut[i] = abs(pIn[i] - pReduce[i]); // 相減,取差值的絕對值
  if(pOut[i] > 32){
   // 相差大於 32 就認為是不同的點,此值因網路攝影機而異,與噪音有關,請自行實驗
   differentPoint ++; // 不同點增加
   pOut[i] = pIn[i]; // 把不同點賦回它的顏色
  }
  if(differentPoint > 200){
   // 不同點大於 200 就認為有情況,應適當改變
   // 調用警報等……
  }
 }
}

  當然了,我可捨不得整天整夜開著電腦守著我睡覺,只是實驗這項技術擷取了卻罷了。
9. 廣闊的影像處理天地

  因為寫程式的需要,也因為濃厚的興趣,我在此段期間找了不少影像處理的資料,不過正如前面說過的,影像處理需要數學,大多資料都有一大堆公式,看不看得懂就得看您的造詣了。雖然我看不懂那些公式,但我也得到了很多有益的啟示,它們是我從更多更新的角度去看待映像,改變了我的思維方式,例像是平面的,但可以把其RGB 分量看作是高度,使圖形呈現"立體模式",從而可以對它應用立體幾何的方法。

  看看我理解的線性放縮吧,這也是下面 D3D 應用的鋪墊。

 

  如,很容易寫出此直線段的方程 y = 2 * x (0<= x <= 10)。我不知道您是怎麼理解此方程的,我自己認為以前一點也不理解"映射"這個概念,現在從線性放縮中明白到:x 被映射到 y 上,長度被拉長了 2 倍。這和單單知道方程是兩回事,我認為這比原來理解要好。利用這個映射就可以把 11 個像素點(下標為 0 ~ 10)的映像放大為 21 個像素點大小,使用下面程式:

for(y = 0; y < 21; y ++){
x = y / 2;
newPicture[y] = oldPicture[x];
}

  就這樣把映像的寬度擴大後再擴大高度就可完成整個映像的放大,縮小也一樣,把直線段的斜率減小就行。直線段代表線性放大,那麼拋物線等曲線呢,分段曲線呢?它們都可以實現放縮,因為都是 x 到 y 的映射,曲線代表的是映射規則。呵呵,都怪中學沒學好!

  在實際放縮時不可能都除得整數,這時就有兩種解決方案,一是插值,上面的右圖就是線性插值的,二是取整數部分,也就是最近點法,這兩種方法的效果和運算量大家都明瞭,自不必我說。

  此放縮原理還可以應用於灰階展開等方面,對比增減就是一個例子。

  現在網路攝影機熱門的人臉跟蹤也是應用影像處理的,其它進階的物體識別、運動分析等都是基於影像處理,其應用前景十分廣闊,您足可投身暢遊其中,只要您有足夠的興趣和知識。使用影像處理時您是否有這樣一個想法

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.