小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(轉(zhuǎn))

 haodafeng_org 2010-09-17
現(xiàn)在的圖像越來越花巧了,有浮雕、馬賽克、相框等特效,看得人眼花繚亂。本來圖像特效沒什么稀奇的,在PhotoShop等圖像處理軟件中我們早已見得多了,不過用在視頻上就令人感覺有點(diǎn)神奇。我一直都想擁有這些效果,但我的攝像頭是很早就買到的,沒福氣奢望驅(qū)動(dòng)程序給 它帶來的全新精彩。剛好我學(xué)習(xí)DirectShow有一段時(shí)間了,既為了挑戰(zhàn)自己(我從未寫過令自己感到滿意的程序),也為了檢驗(yàn)學(xué)習(xí)成果,我就下了決心 用DirectShow實(shí)現(xiàn)這些效果。幾經(jīng)努力,終于有了一些成績,我完成了其中一些效果,并發(fā)現(xiàn)程序可以用在DirectShow支持的影音文件上,又 自己把它應(yīng)用到D3D中去,感覺還不錯(cuò)。

  先看看效果吧,以激勵(lì)斗志。我是對(duì)著攝像頭廣告中的效果圖來做程序的,我怕編程的熱情像以前那樣很快冷卻,只留下一堆亂糟糟的代碼,我需要它來不斷興奮被代碼搞得昏頭轉(zhuǎn)向的大腦。

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖一)VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖二)
(圖一:幾種效果) (圖二:D3D中的攝像展示)

  編程的思路是這樣的:寫一個(gè)DirectShow的VideoRenderer Filter用于實(shí)時(shí)獲取圖像,之后在用DirectShow連接并使用攝像頭、播放視頻時(shí)用上該Filter,這樣就可以實(shí)時(shí)處理視頻的圖片并進(jìn)行顯示了。

  由于要用到DirectShow,我想在這里說一些關(guān)于DirectShow的初級(jí)知識(shí),初學(xué)或沒學(xué)過DirectShow的朋友就請(qǐng)耐下性子來聽我這個(gè)初學(xué)者羅嗦一陣子(要學(xué)DirectShow的朋友可以看天極的DirectShow相關(guān)文章,介紹很詳細(xì))。

  在新版的DX9 SDK中已看不到DirectShow的影子了(DirectShow發(fā)展到了盡頭?),不過DirectShow還可用,而且相當(dāng)有用。聽說VC.net自帶有DX8 SDK開發(fā)文件,但我仍希望您 能找到DX8 SDK和VC6。DX8 SDK幫助文件對(duì)DirectShow詳細(xì)的說明和SDK豐富的DirectShow例子對(duì)DirectShow開發(fā)是大有裨益的。我在開發(fā)Filter 過程中發(fā)現(xiàn)VC.net不能編譯通過,出現(xiàn)“InterLockedExchange 重定義”的編譯錯(cuò)誤,VC6則一切正常,我把這歸咎為微軟的問題,菜鳥的我無力解決。是了,我只用VC6編寫Filter,其它編碼使用的是 VC.net,VC.net可完成一般的DirectShow編程,我本人更喜歡VC.net的編碼環(huán)境。

  “請(qǐng)問Filter是什么?”這個(gè)問題在我腦中很久了,DirectShow的功能是由Filter搭建起來的,但我很久以來都被“Filter”這個(gè)詞困惑著,直到現(xiàn)在才有些眉目。當(dāng)然,我學(xué)識(shí)淺,很可能會(huì)說錯(cuò),請(qǐng)海涵。Filter在影音風(fēng)暴等軟件中稱為“濾鏡”, 在微軟中國上一些譯文把它譯為“篩選器”,五花八門的(看來沒有核心技術(shù)真的很被動(dòng))。而Filter在數(shù)字信號(hào)處理等專業(yè)課程中稱為“濾波器”!這下您 應(yīng)該知道一點(diǎn)了吧,DirectShow對(duì)視頻、音頻的處理過程就是數(shù)字信號(hào)處理的過程,可以把數(shù)字信號(hào)處理的理論應(yīng)用于此,微軟為此把這個(gè) DirectShow部件稱為Filter。

  再說Sample,我把它譯為“采樣”,也就是一個(gè)數(shù)據(jù)包,可理解為攝像頭攝像或聲卡錄音時(shí)每掃描一次得到的數(shù)據(jù)、音視頻文件每一幀的數(shù)據(jù)。

  開發(fā)VideoRenderer Filter

  Filter要做以下工作:接受24bit RGB格式的圖片,這由上級(jí)Filter肢解視頻得到,并把它處理成32bit ARGB圖片,之后傳給外部函數(shù)進(jìn)行進(jìn)一步處理。

我要Filter這樣工作的理由是:幾乎所有的視頻Filter都接受24bit RGB格式,不用擔(dān)心會(huì)連接失??;32bit ARGB可以很好地支持MMX加速,如果你會(huì)用MMX的話,我在本文中會(huì)涉及一點(diǎn)MMX,不過和我水平相同,都是初級(jí)的;調(diào)用外部函數(shù)能提供更多的靈活 性,不用費(fèi)盡心思在Filter中封裝圖像處理函數(shù),可以在寫程序時(shí)隨能力和水平提高而加入新的處理函數(shù),同時(shí)也保證了能夠及時(shí)處理。

  怎么樣,F(xiàn)ilter要做的很少很簡單吧,與此一樣,寫一個(gè)Filter也比想象中的簡單,我們一步步地看看。

  新建一個(gè)簡單的DLL項(xiàng)目,設(shè)置名稱為VR,刪除VR.cpp中的DllMain函數(shù),添加VR.h和VR.def兩個(gè)文件,在VR.def中加入以下代碼,以完成函數(shù)導(dǎo)出。

  LIBRARY VR

  EXPORTS

  DllMain PRIVATE

  DllGetClassObject PRIVATE

  DllCanUnloadNow PRIVATE

  DllRegisterServer PRIVATE

  DllUnregisterServer PRIVATE

  再做些沒有創(chuàng)意的東西 —— Filter注冊、類工廠定義等,在VR.cpp中加入,我是從DirectShow的Filter例子中復(fù)制,再略加修改得來的。

#include "stdafx.h"
#include "VR.h"
#pragma comment(lib,"strmbase.lib")
#pragma comment(lib,"winmm.lib")

// Setup data

const AMOVIESETUP_MEDIATYPE sudIpPinTypes =
{
  &MEDIATYPE_Video, // MajorType
  &MEDIASUBTYPE_NULL // MinorType
};

const AMOVIESETUP_PIN sudIpPin =
{
 L"Input", // The Pins name
 FALSE, // Is rendered
 FALSE, // Is an output pin
 FALSE, // Allowed none
 FALSE, // Allowed many
 &CLSID_NULL, // Connects to filter
 NULL, // Connects to pin
 1, // Number of types
 &sudIpPinTypes // Pin details
};

const AMOVIESETUP_FILTER sudVRAx =
{
 &CLSID_lwVideoRenderer, // Filter CLSID /**/
 L"lwVideoRenderer", // String name /**/
 MERIT_NORMAL, // Filter merit
 1, // Number of pins
 &sudIpPin // Pin details
};

// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance
// function when it is asked to create a CLSID_VideoRenderer object

CFactoryTemplate g_Templates[] = {
 { L"lwVideoRenderer" /**/
 , &CLSID_lwVideoRenderer /**/
 , CVideoRenderer::CreateInstance
 , NULL
 , &sudVRAx },
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
// DllRegisterServer
// Used to register and unregister the filter
STDAPI DllRegisterServer()
{
 return AMovieDllRegisterServer2( TRUE );
} // DllRegisterServer

// DllUnregisterServer

STDAPI DllUnregisterServer()
{
 return AMovieDllRegisterServer2( FALSE );
} // DllUnregisterServer

extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);

// DllMain

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
 return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}// DllMain

  經(jīng)過一番復(fù)制后,需要增加一些簡單的業(yè)務(wù)邏輯。我們先來完成Filter的類定義,從CBaseVideoRendeer派生一個(gè)新類,重寫四個(gè)函數(shù)就可奠定這個(gè)Filter的基本功能,如下,在VR.h中加入。

#include <streams.h>

// 回調(diào)類定義

class FunCLS

{public: virtual void procFun(BITMAPINFO* pBmpInfo, BYTE* pb){return;};
};

// 回調(diào)函數(shù)指針定義

typedef void (CALLBACK* pProcFun)(BITMAPINFO* pBmpInfo,BYTE* pb);

// {F81331DB-2E46-43e7-8709-BE57205D8914} Filter的全局標(biāo)識(shí)符

static const GUID CLSID_lwVideoRenderer =
{ 0xf81331db, 0x2e46, 0x43e7, { 0x87, 0x9, 0xbe, 0x57, 0x20, 0x5d, 0x89, 0x14 } };
 // Filter 類定義
 class CVideoRenderer : public CBaseVideoRenderer
 {
  public:
   // 創(chuàng)建進(jìn)程。
   static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
   // 構(gòu)造、釋構(gòu)函數(shù)
   CVideoRenderer(LPUNKNOWN pUnk,HRESULT* phr);
   ~CVideoRenderer();
  public:
   // 檢查是否有可以接受格式的數(shù)據(jù)
   HRESULT CheckMediaType(const CMediaType* pmt);
   // 設(shè)置具體的數(shù)據(jù)格式,如視頻圖像的寬、高等
   HRESULT SetMediaType(const CMediaType* pmt);
   // 遞交數(shù)據(jù),即顯示、呈現(xiàn)數(shù)據(jù)
   HRESULT DoRenderSample(IMediaSample* pMediaSample);
  private:
   BITMAPINFO m_bmpInfo; // 圖片信息
   BYTE* m_pCopyBuffer; // 復(fù)制緩沖區(qū)
   UINT m_pixelNum; // 像素點(diǎn)的數(shù)目
   FunCLS* m_pFunCLS; // 回調(diào)類指針
   pProcFun m_pPF; // 回調(diào)函數(shù)指針
 };

我在上面曾提過在Filter中要在接受到新數(shù)據(jù)時(shí)調(diào)用外部函數(shù)進(jìn)行處理,因此我定義了一個(gè)回調(diào)類(我自己稱呼的)和一個(gè)回調(diào)函數(shù)指針。這樣可以把回調(diào) 類作為MFC視圖類的一個(gè)基類,以方便地使用MFC視圖類中的成員變量。而同時(shí)提供回調(diào)函數(shù)指針就可以滿足同時(shí)播放多個(gè)視頻文件、使用多個(gè)攝像頭的需要。 這是我在使用中感到有必要而后來修改得來的,使Filter的使用具有足夠的靈活性。下面看看以上Filter類中函數(shù)的具體實(shí)現(xiàn)。

//===========================================================
// 創(chuàng)建進(jìn)程。

CUnknown* WINAPI CVideoRenderer::CreateInstance(LPUNKNOWN pUnk,HRESULT* phr)
{
 return new CVideoRenderer(pUnk,phr);
}

//===========================================================
// 構(gòu)造函數(shù)

CVideoRenderer::CVideoRenderer(LPUNKNOWN pUnk,HRESULT *phr) : CBaseVideoRenderer(CLSID_lwVideoRenderer,"lw Video Renderer",pUnk,phr)
{
 m_pCopyBuffer = NULL;
 m_pFunCLS = NULL;
 m_pPF = NULL;
 m_pixelNum = 0;
}

//===========================================================
// 釋構(gòu)函數(shù)

CVideoRenderer::~CVideoRenderer()
{
 if(this->m_pCopyBuffer){
  delete [] m_pCopyBuffer;
 }
}

//===========================================================
// 檢查媒體類型

HRESULT CVideoRenderer::CheckMediaType(const CMediaType* pmt)
{
 VIDEOINFO *pvi;
 // 只接受視頻
 if( *pmt->FormatType() != FORMAT_VideoInfo ) {
  return E_INVALIDARG;
 }
 // 只接受 RGB24 格式,即 R、G、B各 1 Byte
 pvi = (VIDEOINFO *)pmt->Format();
 if(IsEqualGUID( *pmt->Type(),MEDIATYPE_Video) && IsEqualGUID( *pmt->Subtype(),MEDIASUBTYPE_RGB24)){
  return S_OK;
 }
 return E_INVALIDARG;
}

//===========================================================
// 設(shè)置媒體類型,獲取圖像的各種信息(寬、高等具體信息),處理圖像要用到

HRESULT CVideoRenderer::SetMediaType(const CMediaType* pmt)
{
 VIDEOINFO *pviBmp; // Bitmap info header
 pviBmp = (VIDEOINFO *)pmt->Format();
 memset(&m_bmpInfo,0,sizeof(BITMAPINFO)); // 清零
 m_bmpInfo.bmiHeader = pviBmp->bmiHeader;
 // 改為 32bit,因?yàn)槲視?huì)把它處理成 32bit 的
 m_bmpInfo.bmiHeader.biBitCount = 32;
 // 當(dāng)然,緩沖區(qū)大小也變了
 m_bmpInfo.bmiHeader.biSizeImage = m_bmpInfo.bmiHeader.biSizeImage * 4 / 3;
 // 開辟新 32bit 圖片的緩沖區(qū)
 if(m_pCopyBuffer){ delete [] m_pCopyBuffer;}
 m_pCopyBuffer = new BYTE[m_bmpInfo.bmiHeader.biSizeImage];
 m_pixelNum = m_bmpInfo.bmiHeader.biWidth * m_bmpInfo.bmiHeader.biHeight;
 return S_OK;
}

//===========================================================
// 處理媒體采樣

HRESULT CVideoRenderer::DoRenderSample(IMediaSample* pMediaSample)
{
 // 獲取采樣的數(shù)據(jù)區(qū)指針,即 24bit 圖片的數(shù)據(jù)區(qū)指針
 BYTE* pb = NULL;
 pMediaSample->GetPointer(&pb);
 if(!pb){
  return E_FAIL;
 }
 // 加鎖!鎖住我要操作的數(shù)據(jù)區(qū),以防處理到一半的時(shí)候被打斷而造成錯(cuò)誤
 // 其實(shí)就是多線程編程中經(jīng)常使用的臨界區(qū)的類形式,
 // 利用構(gòu)造函數(shù)和釋構(gòu)函數(shù)來進(jìn)入和退出臨界區(qū)
 // m_RendererLock 是 CBaseVideoRenderer 的成員,繼承得來。
 CAutoLock cAutoLock(&this->m_RendererLock);
 // 把 24bit 圖片處理成 32bit
 BYTE* pb32 = m_pCopyBuffer; // 指向 32bit 緩沖區(qū)的指針
 for(UINT i = 0; i < m_pixelNum; i ++){
  pb32[0] = pb[0];
  pb32[1] = pb[1];
  pb32[2] = pb[2];
  pb32[3] = 0xff; // 0xff 即 255
  pb += 3;
  pb32 += 4;
 }
 // 如果有回調(diào)類,進(jìn)行回調(diào)處理
 if(m_pFunCLS){
  m_pFunCLS->procFun(&m_bmpInfo,m_pCopyBuffer);
 }
 // 如果有回調(diào)函數(shù),進(jìn)行處理
 if(m_pPF){
  m_pPF(&m_bmpInfo,m_pCopyBuffer);
 }
 return S_OK;
}

至此,一個(gè)簡單的 Filter 完成了,可以編譯成功、用regsvr32.exe 注冊并到GraphEdit.exe 中進(jìn)行測試了。不過如果要在程序中使用的話,您會(huì)發(fā)現(xiàn)無法設(shè)置回調(diào)函數(shù)或回調(diào)類。這個(gè)Filter 是如此的無用,除了IBaseFilter 接口的基本功能外我們從它身上得不到任何有用的東西。所以,還得給它寫個(gè)接口,讓我們可以設(shè)置一些東西。寫接口也不是難事,只要有一個(gè)接口的例子,隨便誰 都可以對(duì)照寫出一個(gè)來,我就抄寫了一個(gè)。新建一個(gè) IVRControl.h 文件,加入下面代碼。

// {244DF760-7E93-4cf0-92F4-DCB79F646B7E} 接口的 GUID

static const GUID IID_IVRControl = {0x244df760, 0x7e93, 0x4cf0, {0x92, 0xf4, 0xdc, 0xb7, 0x9f, 0x64, 0x6b, 0x7e}};

// 接口定義

DECLARE_INTERFACE_(IVRControl, IUnknown)
{
 STDMETHOD(GetBmpInfo) (THIS_ // 方法一:獲取圖片信息
  BITMAPINFO** ppBmpInfo ) PURE;

 STDMETHOD(GetPointer) (THIS_ // 方法二:獲取緩沖區(qū)指針
  BYTE** ppb // 緩沖區(qū)指針的指針 ) PURE;

 STDMETHOD(SetFunCLS) (THIS_ // 方法三:設(shè)置回調(diào)類
  FunCLS* pFunCLS // 回調(diào)類指針 ) PURE;

 STDMETHOD(SetFun) (THIS_ // 方法四:設(shè)置回調(diào)函數(shù)
  pProcFun pPF ) PURE;
};

  寫完接口后就要實(shí)現(xiàn)它,在VR.h 中添加 #include "IVRControl.h",而把接口作為Filter 類的一個(gè)基類,像這樣:

class CVideoRenderer : public CBaseVideoRenderer, public IVRControl

  在CVideoRenderer 類中加入接口函數(shù)和詢問接口函數(shù):

// 詢問接口,一般可以不要的,但這里需要使用接口,也重載了
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
// 接口函數(shù)
DECLARE_IUNKNOWN;
STDMETHODIMP GetBmpInfo(BITMAPINFO** ppBmpInfo);
STDMETHODIMP GetPointer(BYTE** ppb);
STDMETHODIMP SetFunCLS(FunCLS* pFunCLS);
STDMETHODIMP SetFun(pProcFun pPF);

  再在VR.cpp 中加入上述函數(shù)的具體實(shí)現(xiàn)代碼:

//===========================================================
// 詢問接口

STDMETHODIMP CVideoRenderer::NonDelegatingQueryInterface(REFIID riid,void** ppv)
{
 CheckPointer(ppv,E_POINTER);
 if(riid == IID_IVRControl){
  // 返回接口。這里有個(gè)細(xì)節(jié):返回接口時(shí),F(xiàn)ilter 的引用計(jì)數(shù)會(huì)增一,所以外部程序用完接口后也要對(duì)它進(jìn)行釋放
  return GetInterface((IVRControl*) this,ppv);
 }else{
  return CBaseVideoRenderer::NonDelegatingQueryInterface(riid,ppv);
 }
}

//===========================================================
// 以下為接口函數(shù)的具體實(shí)現(xiàn),只是簡單的賦值

STDMETHODIMP CVideoRenderer::GetBmpInfo(BITMAPINFO** ppBmpInfo)
{
 *ppBmpInfo = &this->m_bmpInfo;
 return S_OK;
}

STDMETHODIMP CVideoRenderer::GetPointer(BYTE** ppb)
{
 *ppb = m_pCopyBuffer;
 return S_OK;
}

STDMETHODIMP CVideoRenderer::SetFunCLS(FunCLS* pFunCLS)
{
 m_pFunCLS = pFunCLS;
 return S_OK;
}

STDMETHODIMP CVideoRenderer::SetFun(pProcFun pPF)
{
 m_pPF = pPF;
 return S_OK;
}


  不知您注意到了沒有:接口其實(shí)就是一個(gè)虛基類。類在 C++ 等現(xiàn)代編程語言中無處不在,也沒什么好驚奇的,只是有利于更好理解。再有一個(gè),看似功能強(qiáng)大的接口可能偏偏很容易實(shí)現(xiàn),它依附于對(duì)象,它的復(fù)雜可能都隱藏在對(duì)象內(nèi)了。

  可以看出在接口定義中也要用到回調(diào)類和回調(diào)函數(shù)指針的定義,所以我把它們連同 Filter CLSID 的定義一起移到 IVRControl.h 文件的開頭,使用到此 Filter 時(shí)只把 IVRControl.h 這一個(gè)文件包含進(jìn)去就行了。

不錯(cuò),我們已經(jīng)一步步、一個(gè)個(gè)函數(shù)的把設(shè)想中的 Filter 寫出來了,已成功完成了Filter,以 Release 模式把它編譯出來足有80多K,用 UPX 壓縮后就是30 多K。這樣把代碼鋪出來看,好像蠻多的,不過我在敲代碼時(shí)一點(diǎn)也不覺得,因?yàn)槊總€(gè)函數(shù)所做的的確很少,循著邏輯規(guī)矩、步步為營地寫真的很easy。

為方便使用DirectShow而寫一個(gè)封裝類

  如果您使用 DirectShow 有一陣子,您一定會(huì)選擇寫一個(gè)類來封裝 DirectShow,誰也愿意只調(diào)用“PlayMovie”這些只用傳入文件名就能播放的函數(shù)來播放文件,而不想每次都作一大堆初始化和使用一大堆對(duì)象,而且類可以方便的移動(dòng)到不同項(xiàng)目中。

  我在寫這個(gè)類是遇到一個(gè)問題:怎樣使用自寫的 Filter,先注冊再使用還是不必注冊直接手工載入呢?想到綠色軟件是不往注冊表中添加多余信息的,而且要使程序能在進(jìn)行重裝系統(tǒng)等 導(dǎo)致注冊表信息丟失的操作后仍無需重裝而正常運(yùn)行,因此我選擇了手工方式。這樣做也不難,只是模擬一遍 COM 的載入,也就是 CoCreateInstance 工作流程的模擬。我不知道高手們是怎樣做的,這里只是我的做法,流程如下:用 LoadLibrary 載入VR.dll,使用 GetProcAddress 查找 GetClassObject 函數(shù)的地址,再調(diào)用 GetClassObject 得到類工廠,然后用類工廠詢問并得到 Filter。

  先看一個(gè)可以從已載入的 dll 得到對(duì)象的函數(shù):

// 定義 DllGetClassObject 函數(shù)的指針

typedef HRESULT (CALLBACK *lpDllGetClassObject)(REFCLSID,REFCLSID,void**);
HRESULT lwGetClassObject(HMODULE hLib,const CLSID& clsid,const CLSID& riid,void** ppv)
{
 // 裝入和卸載dll由用戶執(zhí)行,以免后來忘記了卸載dll
 HRESULT hr;
 if(!hLib){
  // dll沒有裝載成功
  hr = E_FAIL;
  return hr;
 }
 lpDllGetClassObject lwDGCO = NULL;
 lwDGCO =(lpDllGetClassObject)GetProcAddress(hLib,"DllGetClassObject");
 if(!lwDGCO){
  // 查找函數(shù)失敗
  hr = E_FAIL;
  return hr;
 }
 IClassFactory* pCF = NULL;
 hr = lwDGCO(clsid,IID_IClassFactory,(void**)&pCF);
 if(!pCF){
  // 獲取類廠失敗
  return hr;
 }
 hr = pCF->CreateInstance(NULL,riid,ppv);
 if(!ppv) hr = E_FAIL;
 pCF->Release();
 return hr;
}

  我在類中作了如下這些定義:

// 類成員的定義,要包含“IVRControl.h”文件

IGraphBuilder* m_pGB; // Filter Graph
IBaseFilter* m_pFVR; // 自寫的 Filter
IVRControl* m_pVRControl; // 接口
HMODULE m_hLib; // dll 庫的句柄

  在類的構(gòu)造函數(shù)中載入 dll:

m_hLib = LoadLibrary("VR.dll"); // 載入 dll,“VR.dll”要和程序在同一目錄

  之后在類的成員函數(shù) InitDS 中做獲取 Filter 等工作:

HRESULT hr;
// 創(chuàng)建 Filter Graph
hr = CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC,IID_IGraphBuilder,(void**)&m_pGB);
if(FAILED(hr)){
 return hr;
}

// 調(diào)用上面的函數(shù)從已載入的 dll 中獲取自寫的 Filter

hr = lwGetClassObject(m_hLib,CLSID_lwVideoRenderer,IID_IBaseFilter,(void**)&m_pFVR);
if(FAILED(hr)){
 return hr;
}

// 詢問接口

hr = m_pFVR->QueryInterface(IID_IVRControl,(void**)&m_pVRControl);

if(FAILED(hr)){
 return hr;
}

// 把 Filter 加入 Filter Graph

hr = m_pGB->AddFilter(m_pFVR,L"VideoRenderer");

if(FAILED(hr)){
 return hr;
}

最后在類的釋構(gòu)函數(shù)中卸載dll。本來我在一本2000年版的《COM 精髓》中看到要先調(diào)用 dll 的 DllCanUnloadNow 函數(shù)確定是否應(yīng)卸載才實(shí)行卸載的,不過試驗(yàn)中,特別是使用多個(gè)類播放幾個(gè)不同文件時(shí)發(fā)現(xiàn)就這樣直接卸載就行,用 DllCanUnloadNow 作為卸載條件反而不能正確卸載。

if(m_hLib){
 // 確認(rèn)成功載入了 dll
 FreeLibrary(m_hLib);// 卸載 dll
}

以上的代碼有點(diǎn)亂,請(qǐng)對(duì)照流程耐心的看一下,我已作了詳細(xì)的注釋,應(yīng)該不難看懂。完成這些后,要播放視頻文件只需 m_pGB->RenderFile(L”c:\\sample.wmv”,0); 就可以建立“Filter Chain”了,再用 m_pMC->Run(); 就可開始播放(m_pMC 為 IMediaControl對(duì)象),完全是最一般的 DirectShow 操作,攝像頭控制也一樣,我不多貼代碼了,您可到我拙劣的程序中看。這兒要提的是接口,在上面代碼中如無意外我們已經(jīng)獲得了 Filter 的接口,不過到目前為止這個(gè)接口只通到此 DirectShow 類中,也就是只能在此類中使用,我們還得把它導(dǎo)通到類外以便設(shè)置函數(shù),它的傳遞代碼如下,僅以 SetFun 為例:

HRESULT CDSControl::SetFun(pProcFun pPF)
{
 if(m_pVRControl){
  return m_pVRControl->SetFun(pPF);
 }
 return E_FAIL;
}

圖像處理

  在我的程序中圖像處理函數(shù)是作為 DirectShow 封裝類一部分的,我認(rèn)為這樣便于移動(dòng)和使用。沒有連著上面的 DirectShow 類而另外寫標(biāo)題是因?yàn)槲矣X得有必要把它提到重要位置。

在程序完善階段您的工作基本就在這里了,除了花心思構(gòu)造D3D環(huán)境外幾乎所有效果都要在這里實(shí)現(xiàn),水平高下也體現(xiàn)于此。 在網(wǎng)上可以找到很多圖像特效的代碼和解說,我結(jié)合編程過程再說說。

  1. 訪問緩沖區(qū)的麻煩。

這是最麻煩的,二維圖像在這里以一個(gè)連續(xù)的一維緩沖區(qū)呈現(xiàn),您要靠一個(gè)指針去訪問它,怎么辦呢?先弄懂 Pitch,例如 32bit ARGB 圖像,每個(gè)象素就占用 4 Byte 內(nèi)存空間(1 Byte = 8 bit),對(duì)于寬度為 20 像素的圖像,它的 Pitch 就是 80 Byte,即每一行占用的內(nèi)存。按 x 、y 坐標(biāo)就有如下公式(按 Byte 計(jì)算):

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

  可以看出在內(nèi)存中是按 BGRA 存儲(chǔ)的,我不明白為什么這樣,可能可以從計(jì)算機(jī)的內(nèi)存存儲(chǔ)方式找到答案。上述公式計(jì)算多,效率較低,在實(shí)際使用中應(yīng)適時(shí)作有益的改變。

  2. 浮雕。

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

原理是這樣了,到了編程卻是另外一回事:能夠把規(guī)則、數(shù)學(xué)公式轉(zhuǎn)換為程序也是能力的一種體現(xiàn)。如果要減去右下角的點(diǎn),那么最右一列和最后一行是要特殊處 理的,否則肯定會(huì)發(fā)生內(nèi)存訪問錯(cuò)誤,想一想就知道為什么;如果要減去左邊的點(diǎn),第一列也要特殊處理,請(qǐng)問第一列的點(diǎn)到哪里找它左邊的點(diǎn)呢?不要小視此問 題,它會(huì)令你訪問內(nèi)存時(shí)遇到一些問題。

  3. 鉛筆畫

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

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

  4. 加亮、對(duì)比度等。

  首先悲痛的說明,我曾努力的要實(shí)現(xiàn)色度、飽和度的調(diào) 整,知道是要把 RGB 轉(zhuǎn)換成 HLS 之類的顏色空間才能實(shí)現(xiàn),也找到了一些它們之間轉(zhuǎn)換的說明和轉(zhuǎn)換函數(shù),可惜看不明白,或者說那些材料根本不打算讓我明白!這不單是氣話,而且事實(shí),我真的 十分氣憤:怎么能夠在前面鋪了一大堆“效果圖”說了一大堆廢話然后給個(gè)只有幾行無大用的注釋的代碼就可以呢?!盡管如此憤概,我還是乖乖的抄了程序,希冀 能發(fā)揮作用,結(jié)果卻是失望:不僅效率低下,而且在調(diào)整了飽和度的同時(shí)使圖像出現(xiàn)不協(xié)調(diào)的彩色方塊。由于不知道原理,無法改動(dòng),于是我放棄了它。 調(diào)整亮度很簡單,例如要加亮10,把RGB 都加上 10 就可以了,減亮就減10。

  對(duì)比度調(diào)整也不難,書上說是要令亮點(diǎn)更亮、暗點(diǎn) 更暗,好像是要找出亮點(diǎn)來增亮、找出暗點(diǎn)加暗,其實(shí)不然,把所有點(diǎn)都乘以一個(gè)數(shù),把亮暗點(diǎn)的差距拉大或減小就能調(diào)整對(duì)比度了。把圖像原來的對(duì)比度定為 1,要增大對(duì)比度就調(diào)整為 1.3 、2 等大于1 的數(shù),把每個(gè)點(diǎn)的 RGB 都乘以它,就行了,要降低就把數(shù)值設(shè)為 0 至 1 的數(shù)。只要注意保持 RGB 的值在 0 ~ 255 中即可。

  5. 馬賽克

  馬賽克效果就是把圖像分解成 m * n 個(gè)小塊 或長寬為 x 、y 的小塊,用小塊內(nèi)的某點(diǎn)顏色作為整塊的顏色,通常用左上角的顏色。

動(dòng)起手編程會(huì)很麻煩,要定位到每小塊的左上角,才能改變塊內(nèi)的顏色,因此要用很多循環(huán),我在代碼中就用了12個(gè)循環(huán)!除此,還有邏輯麻煩,拿分成寬高為 x、y 的小塊這種情況為例,您不能保證圖像的長寬剛好都是 x、y的倍數(shù),很多時(shí)會(huì)余出一些“邊角料”,這就是麻煩,不可能舍棄它們不進(jìn)行處理,因?yàn)楹苡绊懶Ч?。因此如圖所示,要先處理藍(lán)色的倍數(shù)部分,再處理綠色 的寬度上余下部分,處理紅色的高度上余下部分,還有黃色的寬高夾縫的小塊。

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖三)VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖四)

  除了這種長方形的處理,還可以試驗(yàn)上圖菱形等形狀,當(dāng)然,您要付出很大的勞動(dòng),而我沒能做到這些。

  6. MMX。

記得在上面我說過會(huì)在文章中涉及一點(diǎn)MMX,不妨在這里涉及。在 VC 中可以鑲?cè)雲(yún)R編使匯編變得很容易,完全不是純匯編代碼所能相比的,所以不用怕匯編,可以先用 C 語言寫出實(shí)現(xiàn)代碼,再用匯編“翻譯”過來。如果譯不出來,更加可以把代碼中斷一下,讓 VC 反匯編,看 VC 的匯編代碼,再行改進(jìn),為什么不行呢,有人用槍指著您么?記住哦,如果沒辦法改進(jìn)就放棄匯編,不要做多余的事。其實(shí)要用 MMX 也不一定非用匯編不可,VC 也提供了 MMX 的 C++ 封裝,學(xué)習(xí)后可用它,我則懶于學(xué)習(xí)。

  MMX 最大的好處是可以自動(dòng)保證處理的值范圍為0 ~ 255,節(jié)省判斷,而且MMX寄存器是64位的,一次可處理32bit圖像的兩個(gè)點(diǎn)。其它的我也不太懂,您可參考相關(guān)資料。

  下面列出浮雕效果代碼,它是減去右邊點(diǎn)的,由于不進(jìn)行行列判斷,每行最后一點(diǎn)減去的是下一行的首點(diǎn)。

__int64 Mask = 0x8080808080808080; // 0x80 = 128,就是亮度的增加值

UINT a = bmpBufferLen >> 3; // 緩沖區(qū)長度(按 BYTE 計(jì)算)除以 8(兩個(gè)點(diǎn)的大?。?,計(jì)算要循環(huán)的次數(shù)

_asm{

mov esi,pIn; // 要處理的緩沖區(qū)指針

mov edi,pOut; // 結(jié)果緩沖區(qū)指針

mov eax,a; // 循環(huán)次數(shù)

dec eax; // 循環(huán)次數(shù)減一,因?yàn)樽詈髢牲c(diǎn)沒法減,可以在后面特殊處理,這里不作處理

movq mm1,Mask; // 增加值,movq 是 MMX 的專用匯編指令,請(qǐng)找資料看

_loop: // 循環(huán)

mov ecx,esi; // ecx 存儲(chǔ)右邊點(diǎn)的指針

add ecx,4; // 只加 4 就跳過一點(diǎn)到右邊點(diǎn)了

movq mm0,[esi]; // 移動(dòng)要處理的兩點(diǎn)的值到 MMX 寄存器

movq mm2,[ecx]; // 移右邊兩點(diǎn)的值

psubusb mm0,mm2; // 相減

paddusb mm0,mm1; // 加上增加值

movq [edi],mm0; // 移到結(jié)果緩沖區(qū)

add esi,8; // 移動(dòng)到下兩點(diǎn)

add edi,8; // 同上

dec eax; // 循環(huán)計(jì)數(shù)減一

jnz _loop; // 不為零就繼續(xù)循環(huán)

emms; // 結(jié)束 MMX 使用

}

  7.來點(diǎn)高級(jí)的,用攝像頭控制鼠標(biāo)!

  看著這個(gè)有點(diǎn)神奇吧,其實(shí)比什么都要簡單。先做好“硬件準(zhǔn)備”:把攝像頭如圖擺放,鏡頭下方放張白紙以使圖像中物體界限分明。

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖五)

  軟件方面,把圖片作閥值處理—— B 值大于 128 的設(shè)為黑色,其它的設(shè)為白色。因?yàn)榘准埖淖饔?,您的手或其他物體會(huì)在閥值圖中顯示為白色,如下圖,再找出圖中紅點(diǎn),即第一點(diǎn)白點(diǎn)的在圖中位置(x,y),再把圖的坐標(biāo)影射到屏幕坐標(biāo)就行了。

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖六) VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖七)

  下面要討論具體做法。先解決圖像坐標(biāo)問題。我獲取第一點(diǎn)白點(diǎn)的程序如下:

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){ // 因?yàn)榘咨珵椋?55,255,255),判斷一個(gè)255 即可
    pb[2] = 255; // 設(shè)為紅色,別忘了 BGRA 的內(nèi)存排列方式
    pb[1] = pb[0] = 0; //
    // 計(jì)算坐標(biāo)
    *xPos = ScreenWid - x * ScreenWid / m_bmpWid;
    *yPos = y * ScreenHei / m_bmpHei;
    mouseFound = TRUE;
    break;
   }
   pb += 4;
  }
  if(mouseFound){
   break;
  }
 }
}

  可以看出我是從所得的圖像緩沖區(qū)的第一點(diǎn)開始檢索的,這也是攝像頭掃描 CCD 的順序,看上面右邊的圖,那是右手的圖像,可以推想出攝像頭的掃描順序如下圖所示:

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖八)VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖九)
(攝像頭掃描坐標(biāo)) (與屏幕坐標(biāo)(藍(lán))相對(duì)的攝像頭坐標(biāo)(紅))

  而相對(duì)于屏幕坐標(biāo)很容易得到上面右圖,說明屏幕坐標(biāo)以左上角為原點(diǎn),x的正方向?yàn)橛?,而攝像頭坐標(biāo)以右上角為原點(diǎn),x的正方向?yàn)樽螅簿褪钦f我按順序?qū)ふ宜玫降淖鴺?biāo)值是這樣的:y 與屏幕坐標(biāo) y 相符,x 則與屏幕坐標(biāo) x 剛好相反。因此推算出鼠標(biāo)位置應(yīng)該是:

*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 當(dāng)然是圖片的寬和高了。得到鼠標(biāo)坐標(biāo)后再用 SetCursorPos 設(shè)置即可,只不過鼠標(biāo)晃動(dòng)會(huì)比較厲害,這與圖像噪音有關(guān),可能先做個(gè)柔化處理會(huì)好一點(diǎn),但鑒于攝像頭的攝像質(zhì)量,我不想作無謂的掙扎,您不會(huì)真的想用攝像 頭代替鼠標(biāo)吧?!

  在使用前應(yīng)該調(diào)整二值圖的閥值,使整個(gè)圖都變成黑色,保證能正確濾除干擾,不然在按下“鼠標(biāo)控制”按鈕后您的鼠標(biāo)就不會(huì)聽話,您會(huì)無法控制好它。請(qǐng)問沒有鼠標(biāo)的幫忙您將如何關(guān)閉程序?對(duì)了,“Alt + F4”,別忘了,否則您得硬著動(dòng)手把攝像頭拔掉??!

可以說控制鼠標(biāo)真的很容易實(shí)現(xiàn),不過效果出奇的不錯(cuò),這種好像無影無蹤的控制方式相當(dāng)令人驚奇,記得我的大哥看程序時(shí)對(duì)我前面的圖像處理沒有一絲反應(yīng), 看到這個(gè)卻大大的驚奇!呵呵。如果您有興趣的話可以在此方面做更多的試驗(yàn),例如可以把手裁剪出來,讓它參加撥動(dòng)一個(gè)小球等游戲,只要您的幾何過關(guān)、有毅力 就可以實(shí)現(xiàn)。

  8.更實(shí)用的數(shù)字減影技術(shù)

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十) VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十) VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十二)

  請(qǐng)看上面三幅圖,左邊的是先存儲(chǔ)一幅背景圖然后把手放到攝像頭前攝像,用攝到的圖片減去背景圖得到的圖;中圖是根據(jù)左圖把手的顏色設(shè)為原來顏色得到 的;右圖是不斷用新圖片減去上一幅圖得到的手移動(dòng)痕跡圖。這充分顯示了數(shù)字減影的功用:能夠從背景分解物體和偵測到物體的運(yùn)動(dòng)。

  如此說來此技術(shù)在安保方面的應(yīng)用會(huì)很突出,像上面右圖那樣不斷減去上一幅圖片,當(dāng)減影后得到的圖片差別大于某一程度的點(diǎn)多到一定數(shù)值時(shí)就說明有情況發(fā)生,這時(shí)候就提醒保安工作,彌補(bǔ)保安的人為失誤,也可以在此時(shí)啟動(dòng)錄像錄取有價(jià)值的情況。請(qǐng)看程序:

void CDSControl::DNS(BYTE* pIn,BYTE* pReduce,BYTE* pOut)
{
 // pIn 新圖的數(shù)據(jù)區(qū)指針,pReduce 背景指針,pOut 存儲(chǔ)區(qū)指針
 if(!pReduce) return; // 沒有背景圖就不處理
 int differentPoint = 0;
 for(int i = 0; i < m_bmpBufferLen; i ++){
  // m_bmpBufferLen 為數(shù)據(jù)區(qū)長度
  pOut[i] = abs(pIn[i] - pReduce[i]); // 相減,取差值的絕對(duì)值
  if(pOut[i] > 32){
   // 相差大于 32 就認(rèn)為是不同的點(diǎn),此值因攝像頭而異,與噪音有關(guān),請(qǐng)自行試驗(yàn)
   differentPoint ++; // 不同點(diǎn)增加
   pOut[i] = pIn[i]; // 把不同點(diǎn)賦回它的顏色 }
  if(differentPoint > 200){
   // 不同點(diǎn)大于 200 就認(rèn)為有情況,應(yīng)適當(dāng)改變
   // 調(diào)用警報(bào)等……
  }
 }
}

  當(dāng)然了,我可舍不得整天整夜開著電腦守著我睡覺,只是試驗(yàn)這項(xiàng)技術(shù)獲取了卻罷了。

  9. 廣闊的圖像處理天地

因?yàn)閷懗绦虻男枰惨驗(yàn)闈夂竦呐d趣,我在此段期間找了不少圖像處理的資料,不過正如前面說過的,圖像處理需要數(shù)學(xué),大多資料都有一大堆公式,看不看得 懂就得看您的造詣了。雖然我看不懂那些公式,但我也得到了很多有益的啟示,它們是我從更多更新的角度去看待圖像,改變了我的思維方式,例如圖像是平面的, 但可以把其RGB 分量看作是高度,使圖形呈現(xiàn)“立體模式”,從而可以對(duì)它應(yīng)用立體幾何的方法。

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

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十三)VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十四)

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

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

  就這樣把圖像的寬度擴(kuò)大后再擴(kuò)大高度就可完成整個(gè)圖像的放大,縮小也一樣,把直線段的斜率減小就行。直線段代表線性放大,那么拋物線等曲線呢,分段曲線呢?它們都可以實(shí)現(xiàn)放縮,因?yàn)槎际?x 到 y 的映射,曲線代表的是映射規(guī)則。呵呵,都怪中學(xué)沒學(xué)好!

  在實(shí)際放縮時(shí)不可能都除得整數(shù),這時(shí)就有兩種解決方法,一是插值,上面的右圖就是線性插值的示意圖,二是取整數(shù)部分,也就是最近點(diǎn)法,這兩種方法的效果和運(yùn)算量大家都明瞭,自不必我說。 此放縮原理還可以應(yīng)用于灰度拉伸等方面,對(duì)比度增減就是一個(gè)例子。

  現(xiàn)在攝像頭熱門的人臉跟蹤也是應(yīng)用圖像處理的,其它高級(jí)的物體識(shí)別、運(yùn)動(dòng)分析等都是基于圖像處理,其應(yīng)用前景十分廣闊,您足可投身暢游其中,只要您有足夠的興趣和知識(shí)。使用圖像處理時(shí)您是否有這樣一個(gè)想法:軟件讓硬件價(jià)值倍增!

應(yīng)用到D3D中去

  平面的圖像、影片看的多了,我們不妨到3D 環(huán)境中看看影片。

  我不會(huì)在這里介紹 D3D,您要學(xué)習(xí)它就得自己找資料,這里只是講在 3D 環(huán)境中播放的關(guān)鍵—— 圖片到紋理。

3D 紋理有一個(gè)特點(diǎn):寬高都必須是 2 的倍數(shù)。您是知道的,通常影像都是 320 * 240 等大小的,把這個(gè)寬高傳入創(chuàng)建得到的紋理卻是 512 * 256 大小的。所以非得把影像圖片拉伸到紋理不可,不然您得到的紋理會(huì)有一片黑色,談不上美觀,盡管我的審美能力讓我不覺得黃金分割很美麗,但我敢肯定這樣的紋 理很丑陋。在我的程序中利用了最近點(diǎn)法把圖像拉伸為紋理大小。不過這樣也引起圖像寬高比改變,造成一定程度的扭曲,您可自寫個(gè)程序把圖像保持寬高比拉伸。 請(qǐng)看看如何動(dòng)態(tài)改變紋理。

HRESULT d3d::SetTex(BYTE* pb)
{
 if(!pTexture) return E_FAIL; // 紋理創(chuàng)建不成功
 if(!pb) return E_FAIL; // 指針錯(cuò)誤
 // 鎖定紋理
 D3DLOCKED_RECT d3dlr;
 if (FAILED(pTexture->LockRect(0, &d3dlr, 0, 0)))
  return E_FAIL;
 BYTE* pTexBits = (BYTE*)d3dlr.pBits; // 取紋理數(shù)據(jù)區(qū)指針
 UINT texPitch = d3dlr.Pitch; // 紋理的 Pitch
 UINT bmpPitch = bmpWid * 4; // 圖片的 Pitch

 float xStep = float(bmpWid - 1) / float(texWid - 1); //
 float yStep = float(bmpHei - 1) / float(texHei - 1); //

 BYTE* pNewBits = pTexBits;
 BYTE* pOldBits = pb;

 BYTE* pNewPixel;
 BYTE* pOldPixel;
 // 最近點(diǎn)放大
 for(int y = 0; y < texHei; y ++){
  pOldBits = pb + int(yStep * y) * bmpPitch; // 定位 y
  pNewBits = pTexBits + y * texPitch;
  for(int x = 0; x < texWid; x ++){
   pPixel = pOldBits + 4 * int(xStep * x);// 定位 x
   pNewPixel = pNewBits + 4 * x;
   pNewPixel[0] = pOldPixel[0];
   pNewPixel[1] = pOldPixel[1];
   pNewPixel[2] = pOldPixel[2];
   pNewPixel[3] = 255;// 紋理的 alpha 值,如果啟用透明,可更改實(shí)現(xiàn)透明效果
  }
 }
 // 解鎖紋理

 if (FAILED(pTexture->UnlockRect(0)))
  return E_FAIL;
 return S_OK;
}

  其中 pTexture 是專為影像而設(shè)置的紋理,其他不明來歷的變量也是 d3d 類的成員,在下面函數(shù)中被賦值。

HRESULT d3d::CreateTex(int wid,int hei)
{
 // 根據(jù)傳入的寬高創(chuàng)建紋理
 if(FAILED(D3DXCreateTexture(this->m_pd3dDevice,wid,hei,1,0,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,&pTexture))){
  return E_FAIL;
 }

 // 紋理描述

 D3DSURFACE_DESC ddsd;
 if ( FAILED(pTexture->GetLevelDesc( 0, &ddsd ) ) ) {
  return E_FAIL;
 }

 // 核對(duì)紋理格式,規(guī)定為 A8R8G8B8 的 32bit ARGB 格式

 if(ddsd.Format != D3DFMT_A8R8G8B8){
  pTexture->Release();
  pTexture = NULL;
  return E_FAIL;
 }
 texWid = ddsd.Width; // 紋理寬
 texHei = ddsd.Height;// 紋理高
 bmpWid = wid; // 圖片寬
 bmpHei = hei; // 圖片高
 return S_OK;
}

我不想每次使用紋理時(shí)創(chuàng)建一個(gè)新的,每次渲染后就釋放它,這會(huì)影響性能,所以在使用中先在恰當(dāng)?shù)牡胤?,例如按下播放按鈕后就創(chuàng)建紋理,然后在有新影像圖 片到來時(shí)更新紋理。我用的是Direct3D8,并非Direct3D9,因?yàn)槲业?GF4(我很想它消失了,可嘆袋中空空如也)運(yùn)行 D3D9 很慢,簡單的場景用 D3D8 也足夠了。附加一句,美妙的場景可使影片播放增色不少。

  另外,我也試過在OpenGL中使用影片紋理,不過我的方法使 CPU 占用率達(dá) 100%,但有一點(diǎn)可以肯定,這同樣可應(yīng)用于OpenGL。

敬告:如果您有興趣讀我的程序,請(qǐng)不要試圖通過看我的 d3d 類來學(xué)習(xí) Direct3D,那只是我前段時(shí)間為了學(xué) D3D 而搭的框架類,有很多不規(guī)范的地方,而且 3D 物體更是一團(tuán)糟,所寫的代碼是臨時(shí)性的,根本沒考慮可讀性,現(xiàn)在我見到它們也很頭痛,不想修整。所以為了不令您對(duì) D3D 產(chǎn)生不好的印象,也為了保持您對(duì) 3D 世界探索的熱情,請(qǐng)另行找資料系統(tǒng)學(xué)習(xí) D3D。對(duì)于我的程序,您只要知道我在哪里使用了 d3d 類的哪些功能來實(shí)現(xiàn)效果就行了,切記!

程序的效率和其他問題

  1. 性能

  在DS 封裝類中我寫了幾個(gè)GDI畫圖函數(shù),可以比較方便的顯示圖片,不過效率低下,我曾發(fā)現(xiàn)它的效能竟比D3D畫 3D場景還差。這是GDI 的問題,大概它沒有很好利用顯卡而依賴CPU的緣故吧。所以我在使用 D3D 的時(shí)候順便用上了 ID3DXSprite 接口,它是在沒有 DirectDraw的情況下很好的2D 畫圖工具,而應(yīng)用了DX 的特性使它效率很高,充分使用顯卡加速,很容易做到旋轉(zhuǎn)、縮放、透明等功能,效率很高。不過應(yīng)注意一個(gè)問題:通常看到的D3D 初始化程序都是為得到最大的3D 效能,即使沒干什么CPU占用率也會(huì)直上到100%,而播放視頻文件一般都需要解碼,CPU 都讓D3D 占去后就沒法流暢解碼視頻,所以應(yīng)該這樣創(chuàng)建D3D 設(shè)備,完成初始化:

m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,
 hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED,&d3dpp,&m_pd3dDevice)

  一定要加上一個(gè)D3DCREATE_MULTITHREADED,讓D3D 解箍 CPU,D3D不忙的時(shí)候降至 0%,當(dāng)然了,3D 效能會(huì)減掉一些,不過效果尚可。

  還有一個(gè),我發(fā)現(xiàn)程序運(yùn)行需要占很多內(nèi)存,但最小化后再打開就只占原來的一半左右,十分想不通。

  2.程序錯(cuò)誤的修正

為了程序正確運(yùn)行,還要解決另一件事。您還記得回調(diào)函數(shù)嗎?就是它惹起了禍?;卣{(diào)函數(shù)帶來了靈活性的同時(shí)也帶來了災(zāi)難。到目前為止我發(fā)現(xiàn)了兩點(diǎn)。一是不 能在里面調(diào)用DirectShow 對(duì)象,例如在里面調(diào)用 IMediaPosition 來設(shè)置進(jìn)度條,這樣當(dāng)按下“停止”時(shí)會(huì)發(fā)現(xiàn) IMediaControl 的Stop 方法不能返回,我認(rèn)為是遞歸調(diào)用所引起的,因此絕對(duì)不能這樣做,無法在Filter內(nèi)采取任何措施防止這種情況。

  第二點(diǎn)直接體現(xiàn)靈活 性弱點(diǎn)。想想我需要在回調(diào)函數(shù)中干些什么,我需要調(diào)用函數(shù)設(shè)置D3D紋理吧,那我就要處理紋理在Filter還運(yùn)作過程中發(fā)生參數(shù)改變或被釋放的問題,在 播放過程中打開新文件或關(guān)閉程序都有可能發(fā)生這種情況。打開新文件時(shí)我們會(huì)先釋放舊的Filter Graph再重建,釋放并創(chuàng)建新的紋理會(huì)在啟動(dòng)播放后才進(jìn)行,因?yàn)橐玫揭曨l參數(shù),所以有可能Filter已經(jīng)運(yùn)作了而使用的紋理還是舊的,引起訪問錯(cuò) 誤。DirectShow 的Filter Chain 有自己的線程,調(diào)用IMediaControl 的Stop 并不能讓它一下子停下來,突然關(guān)閉程序就有可能使紋理釋放比真正結(jié)束播放早,因?yàn)槲覀儾粫?huì)在回調(diào)函數(shù)中管理紋理而在D3D類中進(jìn)行管理,關(guān)閉時(shí)紋理會(huì)隨 D3D類的釋放而釋放。

  我剛解決問題的辦法很稀奇古怪,也許任何一個(gè)大蝦都反對(duì),不過它工作的很好。定義一個(gè)BOOL變量,在打開文 件時(shí)先把它設(shè)為FALSE,結(jié)束打開時(shí)再設(shè)為TRUE,并重載對(duì)話框的OnDestroy函數(shù),在該函數(shù)中把BOOL變量設(shè)為FALSE,這樣在回調(diào)函數(shù) 開始處檢查此變量,為FALSE時(shí)直接返回。后來發(fā)現(xiàn)把回調(diào)函數(shù)設(shè)為NULL也可,而且似乎更好。

  3.與其他實(shí)用程序相比

其實(shí)微軟為DirectShow新配備的VMR9可以無縫與D3D結(jié)合,更有很多強(qiáng)大的功能,絕非我寫的Filter可比,不過我懶于學(xué)習(xí)使用它,自認(rèn) 為寫Filter比學(xué)習(xí)它更容易。從VMR9可以自動(dòng)提供紋理給D3D使用,我萌生一個(gè)想法:能不能在一個(gè)D3D中創(chuàng)建紋理給另一個(gè)D3D 使用呢?我要進(jìn)行驗(yàn)證。

  最近得到一個(gè)叫做“CamTrack”的軟件,它很厲害,自帶的人臉跟蹤功能自不必說,它還能自動(dòng)偵測到攝像 頭的使用,在比Filter更低的層級(jí)就接收并處理了數(shù)據(jù),并把處理后的數(shù)據(jù)作為攝像頭數(shù)據(jù)傳送下來!我驚呆了,好像要從攝像頭的WDM 驅(qū)動(dòng)模型去找突破口,但完全沒有頭緒。

  4.界面

  除了技術(shù)落后、結(jié)構(gòu)混雜之外,我的程序還有很大的一個(gè)缺陷:界面丑陋,元素排列不合理。這是因?yàn)槲也皇煜FC,也不懂得如何設(shè)計(jì)界面造成的,所以您看到的我的程序是古板的win98 樣式,用起來可能很不就手。我初三暑假開始接觸編程,一直用了3年多VB6,鄙視VC,轉(zhuǎn)到VC編程還是近大半年的事,雖已樂不知返卻從沒想過要努力學(xué) MFC,畢竟VC.net的net界面編寫已不再依賴MFC了,它更像VB,更易用,只是程序載入到界面顯示很慢,我用的VC.net2003 是這樣。

  總結(jié)

  在這次編寫中我的體會(huì)良多,記得看過這樣一句話“沒寫過一萬行代碼的項(xiàng)目是不會(huì)懂得軟件工程的”, 此時(shí)真的把它奉為真理了,想我這個(gè)程序才多大點(diǎn)兒,文件已有七八個(gè),平時(shí)我看文件有幾個(gè)的程序都感到頭疼,更不敢想象上萬行程序的管理,可能管理上花的精 力比編碼更多。在標(biāo)準(zhǔn)化工廠里,工人都被分工,他們可能不知道自己在干什么,只有管理人員才對(duì)一切了如指掌。如果生產(chǎn)的是軟件,單會(huì)編碼的程序員就只能做 埋頭的工人了,更高層次由不編碼的管理人員掌握,事實(shí)也是這樣。我還發(fā)現(xiàn)有時(shí)花在界面的時(shí)間比所謂的核心功能還多,正如為了控制發(fā)動(dòng)機(jī)和輪子,汽車在控制 方面要花費(fèi)很多,而在舒適的駕駛環(huán)境和新潮的外型方面更要努力,因?yàn)楸仨氁獮槿怂?、易用、好用。現(xiàn)在回過頭看所寫的Filter,它的確是各種功能的基 礎(chǔ),僅此而已,編碼的比重并不大??偟恼f,我的感想是:現(xiàn)代軟件開發(fā)應(yīng)用的是現(xiàn)代工業(yè)生產(chǎn)模式,標(biāo)準(zhǔn)化、分工合作、流水線操作。當(dāng)然,我的認(rèn)識(shí)還很膚淺, 請(qǐng)指正。 最讓我苦惱的是 DirectShow 不支持 rm 文件,或者說 RealNetworks 不支持 DirectShow,畢竟 rm 的算法是保密的,這導(dǎo)致無法用我的程序欣賞很多美妙的影片,強(qiáng)烈希望大蝦們用real sdk 寫個(gè) Filter 發(fā)布出來,讓我等享用,我找不著real sdk。

  總算勞累后也有成果,總體效果沒有受技術(shù)限制,再請(qǐng)看看幾幅效果圖,以結(jié)全文。程序路上,你我共勉。

VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十五) VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十六)
VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十七) VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十八)
VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖十九) VC+DirectShow對(duì)視頻進(jìn)行圖片處理(圖二十)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多