导航

聚合

«2008年11月»
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

Blog统计

存档

随笔分类

相册

2007年2月4日

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

前几天在帮师兄做一个视频截图的模块,采用了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;
     }

}
 
其中,为了使用directshow,我们除了需要windows.h外,还需要dshow.h,qedit.h和atlbase.h三个头文件,最后再加上一个strmiids.lib库文件。
 
接下来就开启visual c# 2005 express来做一个简单的界面程序。为什么选择c# 来开发界面程序呢?原因很简单,因为c#很简单,同时visual c# 2005 express这样免费又功能强大的工具可以使用。
界面程序很简单,就下面这个样子:
 
       c# 部分调用前面写好的dll函数,实现ddshow的抓图。 moviegrabberdll.cs源代码如下:
using system;
using system.collections.generic;
using system.text;
using system.runtime.interopservices;
using system.drawing;
 
namespace moviegrabbercsharp
{
    
class moviegrabberdll
    
{
        [dllimport(
"moviegrabberdll.dll")]
        
public static extern int fnmoviegrabberdll();
 
        [dllimport(
"moviegrabberdll.dll")]
        
public static extern intptr grabmovieframe(string apath, int graycolorcountthreshold);
 
        
public static bitmap grabmovieframebitmap(string apath,int graycolorcountthreshold)
        
{
            intptr hbitmap 
= grabmovieframe(apath, graycolorcountthreshold);
            
if(hbitmap == intptr.zero)
                
return null;
            
return bitmap.fromhbitmap(hbitmap);
        }

 
        
public static bitmap grabmovieframebitmap(string apath)
        
{
            
return grabmovieframebitmap(apath, 8);
        }

    }

}

 
窗口类mainform.cs的源代码如下:
using system;
using system.collections.generic;
using system.componentmodel;
using system.data;
using system.drawing;
using system.text;
using system.windows.forms;
 
namespace moviegrabbercsharp
{
    
public partial class mainform : form
    
{
        
public mainform()
        
{
            initializecomponent();
        }

 
        
private void openmoviefilepathbutton_click(object sender, eventargs e)
        
{
            openfiledialog dlg 
= new openfiledialog();
            
if (dlg.showdialog() == dialogresult.ok)
            
{
                moviefilepathtextbox.text 
= dlg.filename;
            }

 
        }

 
        
private void grabberbutton_click(object sender, eventargs e)
        
{
            bitmap bitmap 
= moviegrabberdll.grabmovieframebitmap(moviefilepathtextbox.text);
            
if (bitmap != null)
            
{
                messagebox.show(
"抓图成功!");
                grabberpicturebox.sizemode 
= pictureboxsizemode.stretchimage;
                grabberpicturebox.image 
= bitmap;
                grabberpicturebox.invalidate();
                grabberpicturebox.refresh();
            }

            
else
            
{
                messagebox.show(
"失败!");
            }

        }

 
        
private void exitbutton_click(object sender, eventargs e)
        
{
            
this.close();
        }

    }

}

 
编译完成后,我们使用windows里面的一个intro.wmv视频文件来做测试, 具体路径是:c:\windows\system32\oobe\imagee\intro.wmv。之所以选择这个文件作为视频测试文件,因为这个视频 是大家安装完成后winxp后都会自动启动的windows xp的介绍视频,而且这个视频的开始部分是全黑,然后渐渐变亮,再到windows xp的动画部分。如果用windows自带的浏览器看微缩图显示,就是下面这个结果:
       可以看到,这个intro.wmv的微缩图是完全的一张黑色图片,我们并不能看到任何关于视频文件有意义的内容。
       下面启动我们刚才编写的demo视频截图工具来截一下图片,同样这个视频文件,可以看到这个的结果。
      
 
       其中,程序里面默认给出的灰度颜色个数阈值是8,那么就是说,至少图片要有8个不同的颜色灰度值才会截取,而之前的全黑,全白就自然滤过了。

http://www.z6688.com/info/48101-1.htm

发表于 @ 2007年2月4日 21:04 | 评论 (2)

FFmpeg相关下载

FFmpeg
http://ffdshow.faireal.net/mirror/ffmpeg/

pthread32
http://www.signal.uu.se/Toolbox/dream/download.html
ftp://sourceware.org/pub/pthreads-win32/

发表于 @ 2007年2月4日 20:27 | 评论 (349)

转换视频文件为flv媒体格式[转]

视频转换的方法找了不少,最后整个过程记录如下:

转换环境: window2003
转换工具: FFMpeg
转换步骤:
1/ 从 http://ffdshow.faireal.net/mirror/ffmpeg/ 下载最新版本的 FFMpeg.exe (才知道.7z也是种压缩格式)
2/ 解压缩到本地c:\FFMpeg.

3/ 转换视频文件Command Line格式:
转换视频:
ffmpeg.exe -i "e:\input\06.asf" "e:\output\06.mpg" //非flv格式
ffmpeg.exe -i "e:\input\06.asf" -y -ab 56 -ar 22050 -b 500 -r 15 -s 320*240 test.flv //Flv格式
视频抓图:

ffmpeg.exe -i "e:\input\06.asf" -y -f image2 -t 0.001 -s 300*200 "e:\output\02.jpg" //获取静态图


ffmpeg.exe -i "e:\input\06.asf" -vframes 30 -y -f gif "e:\output\02.gif" //获取动态图

4/ 转换为flv文件后,使用Flash编写客户端浏览工具:
4.1/ 新建fla文件,窗口->组建->把FLVPlayback拖拉到画布上.Alt+F7 Skin参数选择播放器外观.
4.2/ 选中FLVPlayback,属性->参数里面把实例名字修改为 FFPlay.图层关键帧写入代码:
FFPlay.contentPath = FilePath;
5/ 嵌入网页的组建增加:<param name="FlashVars" value="FilePath=flv文件路径">和embed里面增加:FlashVars="FilePath=flv文件路径" .前者是for ie的后者是for ff的.

PS.
1/对ffmpeg无法解析的文件格式(WMV9,rm,rmvb),可以先用别的工具转换为avi或者mpg格式.
2/对转换为flv后,客户端工具播放没有时间轴的情况,需要使用 flvmdi.exe 把flv文件增加时间头信息,命令为:

flvmdi.exe "filename.flv" /k




















我也开始研究ffmpeg这个强大的转换工具了
www.uume.com上传视频时应该也是用的这个
刚试着把4M大的avi转成flv,结果flv的体积达到了7M之多,看来是命令行的参数不合适
现转一些有用的东东
部分参数说明
ffmpeg.exe -i F:\闪客之家\闪客之歌.mp3 -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\11.flv
ffmpeg -i F:\01.wmv -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\test.flv
使用-ss参数 作用(time_off set the start time offset),可以从指定时间点开始转换任务。如:
转换文件格式的同时抓缩微图:
ffmpeg -i "test.avi" -y -f image2 -ss 8 -t 0.001 -s 350x240 'test.jpg'
对已有flv抓图:
ffmpeg -i "test.flv" -y -f image2 -ss 8 -t 0.001 -s 350x240 'test.jpg'
-ss后跟的时间单位为秒
Ffmpeg转换命令
ffmpeg -y -i test.mpeg -bitexact -vcodec h263 -b 128 -r 15 -s 176x144 -acodec aac -ac 2 -ar 22500
-ab 24 -f 3gp test.3gp
或者
ffmpeg -y -i test.mpeg -ac 1 -acodec amr_nb -ar 8000 -s 176x144 -b 128 -r 15 test.3gp
 
 
ffmpeg参数设定解说
-bitexact 使用标准比特率
-vcodec xvid 使用xvid压缩
-s 320x240 指定分辨率
-r 29.97 桢速率(可以改,确认非标准桢率会导致音画不同步,所以只能设定为15或者29.97)
画面部分,选其一
-b <比特率> 指定压缩比特率,似乎ffmpeg是自动VBR的,指定了就大概是平均比特率,比如768,1500这样的
就是原来默认项目中有的
-qscale <数值> 以<数值>质量为基础的VBR,取值0.01-255,约小质量越好
-qmin <数值> 设定最小质量,与-qmax(设定最大质量)共用,比如-qmin 10 -qmax 31
-sameq 使用和源同样的质量
声音部分
-acodec aac 设定声音编码
-ac <数值> 设定声道数,1就是单声道,2就是立体声,转换单声道的TVrip可以用1(节省一半容量),高品质
的DVDrip就可以用2
-ar <采样率> 设定声音采样率,PSP只认24000
-ab <比特率> 设定声音比特率,前面-ac设为立体声时要以一半比特率来设置,比如192kbps的就设成96,转换
君默认比特率都较小,要听到较高品质声音的话建议设到160kbps(80)以上
-vol <百分比> 设定音量,某些DVDrip的AC3轨音量极小,转换时可以用这个提高音量,比如200就是原来的2倍
这样,要得到一个高画质音质低容量的MP4的话,首先画面最好不要用固定比特率,而用VBR参数让程序自己去
判断,而音质参数可以在原来的基础上提升一点,听起来要舒服很多,也不会太大(看情况调整
 
 
例子:ffmpeg -y -i "1.avi" -title "Test" -vcodec xvid -s 368x208 - r 29.97 -b 1500 -acodec aac -ac 2 -ar 24000 -ab 128 -vol 200 -f psp -muxvb 768 "1.***"

解释:以上命令可以在Dos命令行中输入,也可以创建到批处理文件中运行。不过,前提是:要在ffmpeg所在的目录中执行(转换君所在目录下面的cores子目录)。
参数:
-y(覆盖输出文件,即如果1.***文件已经存在的话,不经提示就覆盖掉了)
-i "1.avi"(输入文件是和ffmpeg在同一目录下的1.avi文件,可以自己加路径,改名字)
-title "Test"(在PSP中显示的影片的标题)
-vcodec xvid(使用XVID编码压缩视频,不能改的)
-s 368x208(输出的分辨率为368x208,注意片源一定要是16:9的不然会变形)
-r 29.97(帧数,一般就用这个吧)
-b 1500(视频数据流量,用-b xxxx的指令则使用固定码率,数字随便改,1500以上没效果;还可以用动态码率如:-qscale 4和-qscale 6,4的质量比6高)
-acodec aac(音频编码用AAC)
-ac 2(声道数1或2)
-ar 24000(声音的采样频率,好像PSP只能支持24000Hz)
-ab 128(音频数据流量,一般选择32、64、96、128)
-vol 200(200%的音量,自己改)
-f psp(输出psp专用格式)
-muxvb 768(好像是给PSP机器识别的码率,一般选择384、512和768,我改成1500,PSP就说文件损坏了)
"1.***"(输出文件名,也可以加路径改文件名)

P.S. 版主机器强劲的话,可以多开几个批处理文件,让它们并行处理。
E:\ffmpeg.exe -i I:\1.wmv -b 360 -r 25 -s 320x240 -hq -deinterlace -ab 56 -ar 22050 -ac 1 D:\2.flv


















下边是截图CatchImg方法,可从大多数的视频文件中截图成功,大家可测试;
如果截图不成功,大多是因为视频本身的问题,如编码标准或加了密.
但从在线录制的视频Flv文件中截图,还未发现截图失败;

/// <summary>
/// @从视频文件截图,生成在视频文件所在文件夹
/// 在Web.Config 中需要两个前置配置项:
/// 1.ffmpeg.exe文件的路径
/// <add key="ffmpeg" value="E:\ffmpeg\ffmpeg.exe" />
/// 2.截图的尺寸大小
/// <add key="CatchFlvImgSize" value="240x180" />
/// 3.视频处理程序ffmpeg.exe
/// </summary>
/// <param name="vFileName">视频文件地址,如:/Web/FlvFile/User1/00001.Flv</param>
/// <returns>成功:返回图片虚拟地址; 失败:返回空字符串</returns>
public string CatchImg(string vFileName)
{
//取得ffmpeg.exe的路径,路径配置在Web.Config中,如:<add key="ffmpeg" value="E:\ffmpeg\ffmpeg.exe" />
string ffmpeg=System.Configuration.ConfigurationSettings.AppSettings["ffmpeg"];

if ( (!System.IO.File.Exists(ffmpeg)) || (!System.IO.File.Exists(vFileName)) )
{
return "";
}

//获得图片相对路径/最后存储到数据库的路径,如:/Web/FlvFile/User1/00001.jpg
string flv_img = System.IO.Path.ChangeExtension(vFileName,".jpg") ;

//图片绝对路径,如:D:\Video\Web\FlvFile\User1\0001.jpg
string flv_img_p = HttpContext.Current.Server.MapPath(flv_img);

//截图的尺寸大小,配置在Web.Config中,如:<add key="CatchFlvImgSize" value="240x180" />
string FlvImgSize=System.Configuration.ConfigurationSettings.AppSettings["CatchFlvImgSize"];

System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(ffmpeg);
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;

//此处组合成ffmpeg.exe文件需要的参数即可,此处命令在ffmpeg 0.4.9调试通过
startInfo.Arguments = " -i " + vFileName + " -y -f image2 -t 0.001 -s " + FlvImgSize + " " + flv_img_p ;

try
{
System.Diagnostics.Process.Start(startInfo);
}
catch
{
return "";
}

///注意:图片截取成功后,数据由内存缓存写到磁盘需要时间较长,大概在3,4秒甚至更长;
///这儿需要延时后再检测,我服务器延时8秒,即如果超过8秒图片仍不存在,认为截图失败;
///此处略去延时代码.如有那位知道如何捕捉ffmpeg.exe截图失败消息,请告知,先谢过!
if ( System.IO.File.Exists(flv_img_p))
{
return flv_img;                                            
}

return "";
}

顺便也探讨个问题,就是我无法从ffmpeg.exe捕捉截图失败消息~
不知大家可否有办法取得,我目前只能通过检测图片是否生成来判断成功与否,但时间较慢,因为这个检测程序就让用户要多等大概4,5秒时间.。

发表于 @ 2007年2月4日 20:19 | 评论 (581)

2006年11月14日

Windows Vista RC1(5744)全体验(一)

安装环境:VMWare 5.5.2

虚拟光驱:Alcohol 120%

Windows Vista版本:vista_5744.16384.061003-1945_x86fre_client-lrmcfre_en_cn_dvd.iso

 

用VMWare直接载入ISO作为一个驱动器使用,装了N次都没成功,后来改用虚拟机在HOST中先LOAD,再把该光区添加到虚拟机中,安装成功。

废话不多说,直接看图

 

载入文件,开始安装

 

 

输入序列号:

 

接受License