现在的图像越来越花巧了,有浮雕、马赛克、相框等特效,看得人眼花缭乱。本来图像特效没什么稀奇的,在PhotoShop等图像处理软件中我们早已见得多了,不过用在视频上就令人感觉有点神奇。我一直都想拥有这些效果,但我的摄像头是很早就买到的,没福气奢望驱动
(图一:几种 效果 ) (图二:D3D中的摄像展示) |
#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 |
#include <streams.h> // 回调类定义 class FunCLS {public: virtual void procFun(BITMAPINFO* pBmpInfo, BYTE* pb){return;}; }; // 回调函数指针定义 typedef void (CALLBACK* pProcFun)(BITMAPINFO* pBmpInfo,BYTE* pb); // {F81331DB-2E4**3e7-8709-BE57205D8914} Filter的全局标识符 static const GUID CLSID_lwVideoRenderer = { 0xf81331db, 0x2e46, 0x43e7, { 0x87, 0x9, 0xbe, 0x57, 0x20, 0x5d, 0x89, 0x14 } }; // Filter 类定义 class CVideoRenderer : public CBaseVideoRenderer { public: // 创建进程。 static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *); // 构造、释构函数 CVideoRenderer(LPUNKNOWN pUnk,HRESULT* phr); ~CVideoRenderer(); public: // 检查是否有可以接受格式的数据 HRESULT CheckMediaType(const CMediaType* pmt); // 设置具体的数据格式,如视频图像的宽、高等 HRESULT SetMediaType(const CMediaType* pmt); // 递交数据,即显示、呈现数据 HRESULT DoRenderSample(IMediaSample* pMediaSample); private: BITMAPINFO m_bmpInfo; // 图片信息 BYTE* m_pCopyBuffer; // 复制缓冲区 UINT m_pixelNum; // 像素点的数目 FunCLS* m_pFunCLS; // 回调类指针 pProcFun m_pPF; // 回调函数指针 }; |
//=========================================================== // 创建进程。 CUnknown* WINAPI CVideoRenderer::CreateInstance(LPUNKNOWN pUnk,HRESULT* phr) { return new CVideoRenderer(pUnk,phr); } //=========================================================== // 构造函数 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; } //=========================================================== // 释构函数 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; } //=========================================================== // 设置媒体类型,获取图像的各种信息(宽、高等具体信息),处理图像要用到 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,因为我会把它处理成 32bit 的 m_bmpInfo.bmiHeader.biBitCount = 32; // 当然,缓冲区大小也变了 m_bmpInfo.bmiHeader.biSizeImage = m_bmpInfo.bmiHeader.biSizeImage * 4 / 3; // 开辟新 32bit 图片的缓冲区 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) { // 获取采样的数据区指针,即 24bit 图片的数据区指针 BYTE* pb = NULL; pMediaSample->GetPointer(&pb); if(!pb){ return E_FAIL; } // 加锁!锁住我要操作的数据区,以防处理到一半的时候被打断而造成错误 // 其实就是多线程编程中经常使用的临界区的类形式, // 利用构造函数和释构函数来进入和退出临界区 // m_RendererLock 是 CBaseVideoRenderer 的成员,继承得来。 CAutoLock cAutoLock(&this->m_RendererLock); // 把 24bit 图片处理成 32bit BYTE* pb32 = m_pCopyBuffer; // 指向 32bit 缓冲区的指针 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; } // 如果有回调类,进行回调处理 if(m_pFunCLS){ m_pFunCLS->procFun(&m_bmpInfo,m_pCopyBuffer); } // 如果有回调函数,进行处理 if(m_pPF){ m_pPF(&m_bmpInfo,m_pCopyBuffer); } return S_OK; } |
// {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_ // 方法二:获取缓冲区指针 BYTE** ppb // 缓冲区指针的指针 ) PURE; STDMETHOD(SetFunCLS) (THIS_ // 方法三:设置回调类 FunCLS* pFunCLS // 回调类指针 ) PURE; STDMETHOD(SetFun) (THIS_ // 方法四:设置回调函数 pProcFun pPF ) PURE; }; |
class CVideoRenderer : public CBaseVideoRenderer, public IVRControl |
// 询问接口,一般可以不要的,但这里需要使用接口,也重载了 STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv); // 接口函数 DECLARE_IUNKNOWN; STDMETHODIMP GetBmpInfo(BITMAPINFO** ppBmpInfo); STDMETHODIMP GetPointer(BYTE** ppb); STDMETHODIMP SetFunCLS(FunCLS* pFunCLS); STDMETHODIMP SetFun(pProcFun pPF); |
//=========================================================== // 询问接口 STDMETHODIMP CVideoRenderer::NonDelegatingQueryInterface(REFIID riid,void** ppv) { CheckPointer(ppv,E_POINTER); if(riid == IID_IVRControl){ // 返回接口。这里有个细节:返回接口时,Filter 的引用计数会增一,所以外部程序用完接口后也要对它进行释放 return GetInterface((IVRControl*) this,ppv); }else{ return CBaseVideoRenderer::NonDelegatingQueryInterface(riid,ppv); } } //=========================================================== // 以下为接口函数的具体实现,只是简单的赋值 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; } |
不知您注意到了没有:接口其实就是一个虚基类。类在 C++ 等现代编程语言中无处不在,也没什么好惊奇的,只是有利于更好理解。再有一个,看似功能强大的接口可能偏偏很容易实现,它依附于对象,它的复杂可能都隐藏在对象内了。
可以看出在接口定义中也要用到回调类和回调函数指针的定义,所以我把它们连同 Filter CLSID 的定义一起移到 IVRControl.h 文件的开头,使用到此 Filter 时只把 IVRControl.h 这一个文件包含进去就行了。
不错,我们已经一步步、一个个函数的把设想中的 Filter 写出来了,已成功完成了Filter,以 Release 模式把它编译出来足有80多K,用 UPX 压缩后就是30 多K。这样把代码铺出来看,好像蛮多的,不过我在敲代码时一点也不觉得,因为每个函数所做的的确很少,循着逻辑规矩、步步为营地写真的很easy。
为方便使用DirectShow而写一个封装类
如果您使用 DirectShow 有一阵子,您一定会选择写一个类来封装 DirectShow,谁也愿意只调用“PlayMovie”这些只用传入文件名就能播放的函数来播放文件,而不想每次都作一大堆初始化和使用一大堆对象,而且类可以方便的移动到不同项目中。
我在写这个类是遇到一个问题:怎样使用自写的 Filter,先注册再使用还是不必注册直接手工载入呢?想到绿色软件是不往注册表中添加多余信息的,而且要使程序能在进行重装系统等 导致注册表信息丢失的操作后仍无需重装而正常运行,因此我选择了手工方式。这样做也不难,只是模拟一遍 COM 的载入,也就是 CoCreateInstance 工作流程的模拟。我不知道高手们是怎样做的,这里只是我的做法,流程如下:用 LoadLibrary 载入VR.dll,使用 GetProcAddress 查找 GetClassObject 函数的地址,再调用 GetClassObject 得到类工厂,然后用类工厂询问并得到 Filter。
先看一个可以从已载入的 dll 得到对象的函数:
// 定义 DllGetClassObject 函数的指针 typedef HRESULT (CALLBACK *lpDllGetClassObject)(REFCLSID,REFCLSID,void**); HRESULT lwGetClassObject(HMODULE hLib,const CLSID& clsid,const CLSID& riid,void** ppv) { // 装入和卸载dll由用户执行,以免后来忘记了卸载dll HRESULT hr; if(!hLib){ // dll没有装载成功 hr = E_FAIL; return hr; } lpDllGetClassObject lwDGCO = NULL; lwDGCO =(lpDllGetClassObject)GetProcAddress(hLib,"DllGetClassObject"); if(!lwDGCO){ // 查找函数失败 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 库的句柄 |
m_hLib = LoadLibrary("VR.dll"); // 载入 dll,“VR.dll”要和程序在同一目录 |
HRESULT hr; // 创建 Filter Graph hr = CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC,IID_IGraphBuilder,(void**)&m_pGB); if(FAILED(hr)){ return hr; } // 调用上面的函数从已载入的 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; } |
if(m_hLib){ // 确认成功载入了 dll FreeLibrary(m_hLib);// 卸载 dll } |
HRESULT CDSControl::SetFun(pProcFun pPF) { if(m_pVRControl){ return m_pVRControl->SetFun(pPF); } return E_FAIL; } |
在程序完善阶段您的工作基本就在这里了,除了花心思构造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; // 结果缓冲区指针 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 使用 } |
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; } } } |
(摄像头扫描坐标) (与屏幕坐标(蓝)相对的摄像头坐标(红)) |
*xPos = ScreenWid - x * ScreenWid / m_bmpWid; *yPos = y * ScreenHei / m_bmpHei; |
在使用前应该调整二值图的阀值,使整个图都变成黑色,保证能正确滤除干扰,不然在按下“鼠标控制”按钮后您的鼠标就不会听话,您会无法控制好它。请问没有鼠标的帮忙您将如何关闭程序?对了,“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 就认为有情况,应适当改变 // 调用警报等…… } } } |
|
for(y = 0; y < 21; y ++){ x = y / 2; newPicture[y] = oldPicture[x]; } |
应用到D3D中去
平面的图像、影片看的多了,我们不妨到3D 环境中看看影片。
我不会在这里介绍 D3D,您要学习它就得自己找资料,这里只是讲在 3D 环境中播放的关键—— 图片到纹理。
3D 纹理有一个特点:宽高都必须是 2 的倍数。您是知道的,通常影像都是 320 * 240 等大小的,把这个宽高传入创建得到的纹理却是 512 * 256 大小的。所以非得把影像图片拉伸到纹理不可,不然您得到的纹理会有一片黑色,谈不上美观,尽管我的审美能力让我不觉得黄金分割很美丽,但我敢肯定这样的纹 理很丑陋。在我的程序中利用了最近点法把图像拉伸为纹理大小。不过这样也引起图像宽高比改变,造成一定程度的扭曲,您可自写个程序把图像保持宽高比拉伸。 请看看如何动态改变纹理。
HRESULT d3d::SetTex(BYTE* pb) { if(!pTexture) return E_FAIL; // 纹理创建不成功 if(!pb) return E_FAIL; // 指针错误 // 锁定纹理 D3DLOCKED_RECT d3dlr; if (FAILED(pTexture->LockRect(0, &d3dlr, 0, 0))) return E_FAIL; BYTE* pTexBits = (BYTE*)d3dlr.pBits; // 取纹理数据区指针 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; // 最近点放大 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 值,如果启用透明,可更改实现透明效果 } } // 解锁纹理 if (FAILED(pTexture->UnlockRect(0))) return E_FAIL; return S_OK; } |
HRESULT d3d::CreateTex(int wid,int hei) { // 根据传入的宽高创建纹理 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; } // 核对纹理格式,规定为 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; } |
程序的效率和其他问题
1. 性能
在DS 封装类中我写了几个GDI画图函数,可以比较方便的显示图片,不过效率低下,我曾发现它的效能竟比D3D画 3D场景还差。这是GDI 的问题,大概它没有很好利用显卡而依赖CPU的缘故吧。所以我在使用 D3D 的时候顺便用上了 ID3DXSprite 接口,它是在没有 DirectDraw的情况下很好的2D 画图工具,而应用了DX 的特性使它效率很高,充分使用显卡加速,很容易做到旋转、缩放、透明等功能,效率很高。不过应注意一个问题:通常看到的D3D 初始化程序都是为得到最大的3D 效能,即使没干什么CPU占用率也会直上到100%,而播放视频文件一般都需要解码,CPU 都让D3D 占去后就没法流畅解码视频,所以应该这样创建D3D 设备,完成初始化:
m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL, hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED,&d3dpp,&m_pd3dDevice) |
|