科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基于灰度颜色个数的视频截图选取

基于灰度颜色个数的视频截图选取

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

   前几天在帮师兄做一个视频截图的模块,采用了DirectShow的接口来访问视频文件。开发工具使用的是Visual C++ 2005 Express 和Visual C# 2005 Express。

作者:中国IT实验室 来源:中国IT实验室 2007年10月2日

关键字: 灰度 VC 编程

  • 评论
  • 分享微博
  • 分享邮件
  
前几天在帮师兄做一个视频截图的模块,采用了DirectShow的接口来访问视频文件。开发工具使用的是Visual C++ 2005 Express 和Visual C# 2005 Express,VC++写的一个封装了对DirectShow的接口访问的DLL,然后在C#做的界面程序里面调用。
 
1. 关于DirectShow的视频截图方法
DirectShow以前是属于DirectX内的一个部分,后来Microsoft把DirectShow归入了Platform SDK内了。关于DirectShow如何来截取视频文件内部的图片picture,在网上可以搜索到很多。在MSDN关于DirectShow SDK的教程里面,也有专门举例如何使用DirectShow的IMediaDet接口来截视频流内的截图的:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcedshow/html/_dxce_dshow_directshow.asp
使用DirectShow来访问视频文件就可以避免去了解各种视频压缩文件格式,编码格式等等很繁琐甚至是困难的问题了。
 
2. 选择哪个时间点来截视频图片呢?
但是,选取哪个时间点的视频图片呢?我们在看Windows浏览器里面视频文件的微缩图都是视频文件的第一帧,但是如果第一帧是全黑或者全白呢?那么我们看到的这个截下来的视频图片并没有任何意义。甚至比如电影开头的演员字幕等帧,对于观众来说都没有多大的意义。一部电影的截图选择,如果按照精彩镜头来分,那么需要计算机去理解该电影的内容,这个工作在现阶段来说,涉及到计算机视觉,数字图像,人工智能等前沿技术,不大可能做得出来。
考虑到全黑,全白,以及片头字幕等没有意义的帧图片的特点,就是颜色个数相对较少,相对单调。于是,可以通过一个颜色个数的阈值,来对所有帧图片进行筛选。将颜色个数小于阈值的剔除。一般使用颜色丰富的图片,肯定帧图片更加丰富。
但是,在24位真彩色中,R,G,B都是0-255,任何一个分量相差了一点点,视觉上来说,差异并不大,但是对于计算机来说,就完全是两个颜色了,这种过于精确的颜色统计,对于人来说并不见得好。于是,我选择使用颜色的灰度值来代替真彩色RGB的统计。关于RGB到灰度值的公式,选择的是最简单的:
GRAY(灰度) = (R + G + B) / 3
 
3. 实现一个测试算法的Demo
       好了,大体的截图选取算法思想就是这样了。下面我就一步一步来把这个算法实现的Demo,通过Visual C++ 2005 Express和Visual C# 2005 Express开发工具做出来。
       首先是做封装DirectShow的Win32 DLL。
       Microsoft那里下载的Visual C++ 2005 Express并没有附带Platform SDK,Windows的最新Platform SDK可以直接从Microsoft的MSDN那里下载到(我选择的是Windows 2003 Server RC2)。按照MSDN上所述的,搭建起Visual C++ 2005 Express内的Platform SDK设置后就可以开发Win32的程序了。
       下面是封装的DLL的程序代码:
// MovieGrabberDLL.cpp : 定义 DLL 应用程序的入口点。
//
 
#include 
"stdafx.h"
#include 
"MovieGrabberDLL.h"
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD ul_reason_for_call, 
                       LPVOID lpReserved
                        )
{
     
switch (ul_reason_for_call)
     
{
     
case DLL_PROCESS_ATTACH: 
     
case DLL_THREAD_ATTACH:
     
case DLL_THREAD_DETACH:
     
case DLL_PROCESS_DETACH:
         
break;
     }

    
return TRUE;
}

 
/**
* 抓取视频的截图
* @param aPath 视频文件的位置
* @return
*/

MOVIEGRABBERDLL_API HANDLE GrabMovieFrame(LPCTSTR aPath,
int grayColorCountThreshold)
{
     HRESULT hr;
     
// 定义IMediaDet接口实例
     CComPtr< IMediaDet > pDet;
     hr 
= pDet.CoCreateInstance(__uuidof(MediaDet));
     
if (FAILED(hr))
         
return NULL;
 
     
// 将影片文件名转换成BSTR类型
     CComBSTR openBSTR(aPath);
     
// 设置IMediaDet接口的文件关联
     hr = pDet->put_Filename(openBSTR);
     
if (FAILED(hr))
         
return NULL;
 
     
// 从影片中检索视频流和音频流
     long lStreams;
     hr 
= pDet->get_OutputStreams(&lStreams);
     
if (FAILED(hr))
         
return NULL;
 
     
// 取出影片的视频流,因为帧的信息是保存在视频流中的
     bool bFound = false;
     
for (int i=0; i<lStreams; i++)
     
{
         GUID major_type;
         hr 
= pDet->put_CurrentStream(i);
         
if (SUCCEEDED(hr))
              hr 
= pDet->get_StreamType(&major_type);
         
if (FAILED(hr))
              
break;
         
if (major_type == MEDIATYPE_Video)
         
{
              bFound 
= true;
              
break;
         }

     }

     
if (!bFound)
         
return NULL;
 
     
long width = 0, height = 0// 存储位图的宽和高(单位:象素)
     AM_MEDIA_TYPE mt;
     hr 
= pDet->get_StreamMediaType(&mt);
     
if (SUCCEEDED(hr))
     
{
         
if ((mt.formattype == FORMAT_VideoInfo) && 
              (mt.cbFormat 
>= sizeof(VIDEOINFOHEADER)))
         
{
              
// 得到VIDEOINFOHEADER结构指针,VIDEOINFOHEADER结构包含一些与视频
              
// 有关的信息,其中含有BITMAPINFORHEADER结构
              VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat);
              width 
= pVih->bmiHeader.biWidth;
              height 
= pVih->bmiHeader.biHeight;
              
if(height < 0 ) height *= -1;
         }

         
else
              hr 
= VFW_E_INVALIDMEDIATYPE;
         MyFreeMediaType(mt); 
// 释放AM_MEDIA_TYPE结构
     }

     
if (FAILED(hr))
         
return NULL;
     
return (HANDLE)LookforSuitableMovieFrame(pDet,width,height,grayColorCountThreshold);
}

 
/**
* 写入合适视频帧截图到磁盘
* @param pDet DirectShow的IMediaDet接口
* @param width 截图的长
* @param height 截图的宽
* @param grayColorCountThreshold 灰度颜色个数阈值
*/

HBITMAP LookforSuitableMovieFrame(IMediaDet
* pDet,int width,int height,int grayColorCountThreshold)
{
     
long size;
     
double time = 0.0;
     
double totaltime;
 
     
// 获取整个视频的时间长度
     pDet->get_StreamLength(&totaltime);
     
// 每1秒,截取视频截图
     for(time=0.0; time <totaltime; time+= 1.0
     
{
         
// 获取bitmap的buffer大小
         HRESULT hr = pDet->GetBitmapBits(time, &size, 0, width, height);
         
if (SUCCEEDED(hr)) 
         
{
              
char *pBuffer = new char[size];
              
if (!pBuffer)
                   
return NULL;
              hr 
= pDet->GetBitmapBits(time, 0, pBuffer, width, height);
              
if (SUCCEEDED(hr))
              
{
                   
// Find the address of the start of the image data.
                   void *pData = pBuffer + sizeof(BITMAPINFOHEADER);
                   
if(IsSuitableMovieFrame(pData,width,height,grayColorCountThreshold))
                   
{
                       BITMAPINFOHEADER 
*bmih = (BITMAPINFOHEADER*)pBuffer;
                       HDC hdcDest 
= GetDC(0);
 
                       BITMAPINFO bmi;
                       ZeroMemory(
&bmi, sizeof(BITMAPINFO));
                       CopyMemory(
&(bmi.bmiHeader), bmih, sizeof(BITMAPINFOHEADER));
                       HBITMAP hBitmap 
= CreateDIBitmap(hdcDest, bmih, CBM_INIT, 
                            pData, 
&bmi, DIB_RGB_COLORS);
 
                       delete[] pBuffer;
                       
return hBitmap;
                   }

              }

              delete[] pBuffer;
         }

     }

     
return NULL;
}

 
/**
* 检测一个位图是否是合适的视频截图
* @param pData 位图的点色数组
* @param width 位图的长
* @param height 位图的宽
* @param grayColorCountThreshold 灰度颜色个数阈值
*/

bool IsSuitableMovieFrame(void* pData,int width,int height,int grayColorCountThreshold)
{
     BYTE
* pixels = (BYTE*)pData;
     
int numGrayColor = 0;
     
int size = width*height;
     
int graycolor;
     
int i,j;
     
int* appearedcolors = new int[grayColorCountThreshold];
     
int numappearedcolors = 0;
     
for(i=0;i<size; i++
     
{
         
// 计算当前点的灰度值,采用的RGB转换灰度的公式是GRAY = (R+G+B)/3
         graycolor = (pixels[i*3+pixels[i*3+1]+pixels[i*3+2])/3;
         
// 检测该灰度色是否之前出现过
         for(j=0;j<numappearedcolors; j++
         
{
              
if(graycolor == appearedcolors[j])
                   
break;
         }

         
if(j == numappearedcolors) // 如果是新的灰度颜色值
         {
              numappearedcolors
++;
              
if(numappearedcolors == grayColorCountThreshold) // 如果灰度颜色个数满足阈值
              {
                   delete[] appearedcolors;
                   
return true// 返回信息,合适
              }

              
else
              
{
                   appearedcolors[j] 
= graycolor; // 记录下该灰度颜色值
              }

         }

     }

     delete[] appearedcolors;
     
return false// 返回信息,不合适
}

 
void MyFreeMediaType(AM_MEDIA_TYPE& mt)
{
     
if(mt.cbFormat != 0)
     
{
         CoTaskMemFree((PVOID)mt.pbFormat);
         mt.cbFormat 
= 0;
     }

 
     
if (mt.pUnk != NULL)
     
{
         mt.pUnk
->Release();
         mt.pUnk 
= NULL;
     }

}
 
 

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章