○DirectShowを使ったフレームの切り出しとキャプチャ
DirectShowを用いた動画の取り扱いについて紹介します。
1.COMは必修
前回までに紹介した Windowsでの動画を扱う枠組み Video for WindowsはどちらかというとAPI関数を利用した古典的なプログラミング方法で利用できました。また、従来のCOMも最終的にはWindowsAPI関数に翻訳されて実行されるものが多く、COMを利用しなくともすべてのサービスをAPI関数から利用できました。
ところが、新しいメディアを利用する枠組みDirecShow/DirectXはCOMのみで提供されます。したがってDirecShow/DirectXを利用するにはCOMの使用は避けられません。(図1)
ではCOMとは何でしょうか。一言でいえば「いくつかの約束に従ったクラス定義と
そのインスタンス」ということになります。COMにはクラスに存在するメソッド(関数)
を機能別にまとめたインターフェースという機構があります。COMを利用する場合、C++のクラス異なりクラスのメソッドを直接呼び出すのではなく、このインターフェースを介してCOMのメソッドを呼び出します。また各インターフェースにはCOM内の他のインターフェースを呼び出すためのQueryInterface()メソッドが必ず存在します。(図2)
図1 COMとAPI
図2 COMとインターフェース
2.DirectShowを使ったフレームの切り出し
では1つめのDirectShowの使用例としてAVIファイルからの1フレーム切り出しプログラムを紹介しましす。
DirectShowでは動画や音声データのストリームをフィルタグラフという方法で扱います。フィルタグラフは処理全体をまとめるフィルタグラフマネージャと各フィルタで構成されます。これらはすべてCOMオブジェクトです。
フィルタグラフマネージャは再生すべきメディアが与えられるとその処理に必要な圧縮解凍のためのフィルタなどを自動的に選択しフィルタグラフを構築します。この動作は内部的に見れば、フィルタのCOMオブジェクトがフィルタグラフマネージャによって自動的に起動されフィルタグラフマネージャ配下で動作するということになります。
通常のAVIファイルの再生では、AVIファイルの種類に対応したフィルタとフィルタグラフがフィルタグラフマネージャのIGraphBuilderインターフェースのRenderFile()メソッドが呼ばれて決まります。AVIファイルからの1フレームの切り出しはこのフィルタグラフにグラバフィルタを手動で挿入することで実現します。
グラバフィルタはその名のとうりストリームからデータをつかみだします。このグラバフィルタをどこに挿入するかは、グラバフィルタの入力の種類を設定してからフィルタグラフマネージャに任せます。あとはグラバフィルタからサンプルしたデータを取り出す部分を準備すれば1フレームの切り出しは一応完了です。(図3)
ところが、DV形式のAVIファイルを再生する場合、特に計測等に使用する場合はもう1つ設定すべき項目があります。DV形式のAVIファイルの再生の場合、当然DV用のデコーダフィルタが準備されます。このデコーダフィルタの解像度は何種類かに設定できます。この解像度の設定は不揮発で、フィルタグラフエディタ等のツールで設定することができます。
フィルタグラフマネージャから使用する場合、現在設定されている解像度がそのまま使われます。計測等で詳細な画面を必要とする場合は、手動でこの解像度をFULLに設定する必要があります。
リストAの場合、フィルタグラフマネージャで構築されたフィルタグラフからデコーダフィルタのインタフェースを取り出し、そのインタフェースを用いて設定します。フィルタグラフで使用されるフィルタはフィルタ列挙子を使って調べます。
リストAでのレンダリングは指定のフレームまでシークしそのフレームだけをrun()メソッドを使ってレンダリングし、そのフレームをグラブします。(図4)
図3 リストAのフィルタグラフ
// lista.cpp DirectShowを使ったフレームの切り出し ver1.8b
// ○先月号本文list1.h、list1b.cppを合わせて、
// コンソールアプリケーションで作成すること
// ○[プロジェクト][設定][C/C++]
// [コード生成][ランタイム]にマルチスレッドを設定すること
// ○[プロジェクト][設定][C/C++]
// [プリプロセッサ][インクルードファイルのパス]に
// c:\mssdk\includeを追加
// (c:\mssdkにSDKをインストールした場合)
// ○[プロジェクト][設定][リンク]
// [一般][オブジェクト/ライブラリモジュール]に
// c:\mssdk\lib\strmiids.lib を追加
// (c:\mssdkにSDKをインストールした場合)
// ○COMの操作はすべてHRESULTを返すが、プログラムを簡単にするため
// 最低限の吟味しか行っていない。
#include <windows.h>
#include <string.h>
#include <Dshow.h> // DirectX8のヘッダファイル
#include <qedit.h> // SampleGrabber用
#include <conio.h> // getch()用
#include <stdio.h>
#include "list1.h"
void main( int ac, char *av[] )
{
// インターフェース用のポインタ
// フィルタグラフ用
IGraphBuilder *pigb = NULL;
IMediaControl *pimc = NULL;
IMediaEvent *pimex = NULL;
IMediaSeeking *pims = NULL;
// サンプルグラバ用
IBaseFilter *pF = NULL;
ISampleGrabber *pGrab = NULL; // これらは後で解放すること。
IMG0 img00; // 表示ウィンドウ用の構造体
BYTE *buffer; // 外部バッファ
AM_MEDIA_TYPE amt;
WCHAR filename[ MAX_PATH ];
HRESULT hr;
if( ac != 2 ){
return;
}
img00.hi = (HINSTANCE)GetWindowLong( HWND_DESKTOP, GWL_HINSTANCE );
img00.x = 100; img00.y = 100;
gr_reg(); // 表示用ウィンドウの登録
CoInitialize(NULL); // COMの準備
// FilterGraphの初期化
CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pigb);
// フィルタグラフのインターフェースを得る
pigb -> QueryInterface( IID_IMediaControl, (void**)&pimc );
pigb -> QueryInterface( IID_IMediaEvent, (void **)&pimex );
pigb -> QueryInterface( IID_IMediaSeeking, (void **)&pims );
// グラバフィルタを作りフィルタグラフに追加
CoCreateInstance( CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (LPVOID *)&pF);
pF -> QueryInterface( IID_ISampleGrabber, (void **)&pGrab );
pigb -> AddFilter( pF, L"SamGra" );
// グラバフィルタの挿入場所の特定のための設定
ZeroMemory( &amt, sizeof(AM_MEDIA_TYPE) );
amt.majortype = MEDIATYPE_Video;
amt.subtype = MEDIASUBTYPE_RGB24;
amt.formattype = FORMAT_VideoInfo;
pGrab -> SetMediaType(&amt);
// ファイル名の変換
MultiByteToWideChar( CP_ACP, 0, av[1], -1, filename, MAX_PATH );
// 再生するファイルを指定、この時点で使用するフィルタが決まる
hr = pigb -> RenderFile( filename, NULL );
printf( "RenderFile hr %x\n", hr );
if(hr != 0) return;
// DVデコーダの解像度をFULLに設定する
// デフォルト解像度で使用するなら不要
// デフォルト解像度はFilterGraphEditorでも設定できる
IIPDVDec *pdv = NULL;
IEnumFilters *pEnum = NULL;
IBaseFilter *pFilter;
ULONG cFetched;
int nnn;
FILTER_INFO FilterInfo;
char szName[256];
pigb -> EnumFilters( &pEnum );
// 使用されるフィルタを調べる
while( pEnum -> Next(1, &pFilter, &cFetched) == S_OK) {
pFilter -> QueryFilterInfo( &FilterInfo);
WideCharToMultiByte(CP_ACP, 0, FilterInfo.achName,
-1, szName, 256, 0, 0);
printf("使用フィルタ = %s \n",szName);
if( ! strcmp( szName, "DV Video Decoder") ){
// DVデコーダなら解像度をFULLに設定
pFilter -> QueryInterface( IID_IIPDVDec, (void **)& pdv);
pdv -> get_IPDisplay( &nnn);
printf(" 既設デコード解像度 IP = %d \n",nnn);
pdv -> put_IPDisplay( 1000);
pdv -> get_IPDisplay( &nnn);
printf(" 仮設デコード解像度 IP = %d \n",nnn);
pdv -> Release();
}
FilterInfo.pGraph -> Release();
pFilter -> Release();
}
pEnum -> Release();
// ビットマップ情報の取得
pGrab -> GetConnectedMediaType( &amt );
// ビデオ ヘッダーへのポインタを獲得する。
printf( "amt.lSampleSize = %d \n", amt.lSampleSize );
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)amt.pbFormat;
// ビデオ ヘッダーには、ビットマップ情報が含まれる。
// ビットマップ情報を BITMAPINFO 構造体にコピーする。
BITMAPINFO BitmapInfo;
ZeroMemory( &BitmapInfo, sizeof(BitmapInfo));
CopyMemory( &BitmapInfo.bmiHeader, &(pVideoHeader->bmiHeader),
sizeof(BITMAPINFOHEADER) );
printf( "BitmapInfo.bmiHeader.biBitCount = %d\n",
BitmapInfo.bmiHeader.biBitCount );
img00.bih = BitmapInfo.bmiHeader;
buffer = (BYTE *)malloc(amt.lSampleSize);
img00.lpBmpData=buffer;
// レンダリングとグラブ
long n = amt.lSampleSize;
long evCode;
int n0, n1;
printf( "開始フレーム?" );
scanf( "%d", &n0 );
printf( "終了フレーム?" );
scanf( "%d", &n1 );
LONGLONG nn = n0;
gr_init( &img00 ); // グラフィックウィンドウを生成
pims -> SetTimeFormat( &(TIME_FORMAT_FRAME) ) ;
// シークをフレーム単位で行うよう設定
pGrab -> SetBufferSamples(TRUE); // グラブ開始
for(int i = n0; i <= n1;i++){
// シーク
pims -> SetPositions( &nn, AM_SEEKING_AbsolutePositioning,
&nn, AM_SEEKING_AbsolutePositioning );
pimc -> Run(); // 1フレームだけレンダリング
printf( "run at %d\n", nn );
pimex -> WaitForCompletion( 100, &evCode );
// レンダリング完了を待つ
pGrab -> GetCurrentBuffer( &n, (long *)buffer );
// グラブ
InvalidateRect( img00.hwnd, NULL, FALSE); // 表示
nn++;
}
printf("終了\n");
getch();
// FilterGraph他の開放
pigb -> Release(); pimc -> Release();
pims -> Release(); pimex-> Release();
pF -> Release(); pGrab-> Release();
CoUninitialize();
SendMessage( img00.hwnd, WM_CLOSE, 0, 0 ); // グラフィック画面の終了
free( buffer );
return ;
}
図4 リストAの実行画面
3.DirectShowを使ったデジタルビデオからのキャプチャ
次にデジタルビデオからのキャプチャ方法について紹介します。DirctShowではファイルや画面へのキャプチャを行うためにキャプチャグラフと呼ぶ専用のグラフマネージャが用意されています。キャプチャグラフは前述のフィルタグラフマネージャを配下に組み込みキャプチャ動作全体を制御します。このキャプチャグラフで構築されたフィルタグラフにリストAでも使用したグラバフィルタを組み込めばデジタルビデオ信号を直接フレームのデータとして得ることができます。
通常のキャプチャを行うためには、キャプチャグラフとフィルタグラフマネージャの2つのCOMを生成し、キャプチャグラフにフィルタグラフマネージャを組み込みます。実際にキャプチャに必要なフィルタの選択とフィルタグラフの構築はフィルタグラフマネージャでなくキャプチャグラフのICaptureGraphBuilder2インタフェースのRenderStream()メソッドを使って半分自動で行います。
リストBでは最初にシステムデバイス列挙子(列挙子と日本語訳され演算子のようだが、これもCOM)を使ってビデオデバイスを調べ、フィルタとしてのインタフェースを得ます。
そのあとキャプチャグラフ、フィルタグラフマネージャ、グラバフィルタを生成し
RenderStream()によってこれらによるグラフを構築します。
リストBではRenderStream()でレンダリング出力をグラバフィルタに指定しているためリストA異なりDirectShow側での表示は行われません。キャプチャとグラブは非同期ですが常に行われるようにしています。実際に使用する場合は、キャプチャ側は常に取り込みを行い、グラブを適当な周期、あるいは必要な時点で行うようなプログラムにすればいいでしょう。(図5、図6)
図5 リストBのフィルタグラフ
// listb.cpp DirectShowを使ったキャプチャ ver2.5b
// ○先月号本文list1.h、list1b.cppを合わせて、
// コンソールアプリケーションで作成すること
// ○[プロジェクト][設定][C/C++]
// [コード生成][ランタイム]にマルチスレッドを設定すること
// ○[プロジェクト][設定][C/C++]
// [プリプロセッサ][インクルードファイルのパス]に
// c:\mssdk\includeを追加
// (c:\mssdkにSDKをインストールした場合)
// ○[プロジェクト][設定][リンク]
// [一般][オブジェクト/ライブラリモジュール]に
// c:\mssdk\lib\strmiids.lib を追加
// (c:\mssdkにSDKをインストールした場合)
// ○COMの操作はすべてHRESULTを返すが、プログラムを簡単にするため
// 最低限の吟味しか行っていない。
#include <windows.h>
#include <dshow.h>
#include <stdio.h>
#include <conio.h>
#include <qedit.h> // SampleGrabber用
#include "list1.h"
main()
{
IGraphBuilder * pGraph = NULL;
IMediaControl * pMC = NULL;
ICaptureGraphBuilder2 * pCapture = NULL;
IBaseFilter *pF = NULL;
ISampleGrabber *pGrab = NULL; // これらは後で解放すること。
HRESULT hr;
IMG0 img00;
BYTE *buffer;
AM_MEDIA_TYPE amt;
CoInitialize(NULL); // COMの初期化
// ---- キャプチャフィルタの準備 ----
// キャプチャデバイスを探す
IBaseFilter *pbf = NULL;
IMoniker * pMoniker = NULL;
ULONG cFetched;
// デバイス列挙子を作成
ICreateDevEnum * pDevEnum =NULL;
CoCreateInstance( CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
IID_ICreateDevEnum, (void ** ) &pDevEnum);
// ビデオキャプチャデバイス列挙子を作成
IEnumMoniker * pClassEnum = NULL;
pDevEnum -> CreateClassEnumerator(
CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
if (pClassEnum == NULL){
printf("ビデオキャプチャデバイスは存在しません\n");
pDevEnum -> Release();
CoUninitialize();
return ;
}
// 最初に見つかったビデオキャプチャデバイスのオブジェクトの
// インタフェースを得る
pClassEnum -> Next(1, &pMoniker, &cFetched);
pMoniker -> BindToObject( 0, 0, IID_IBaseFilter, (void**)&pbf );
// ---- フィルタグラフの準備 ----
// フィルタグラフを作り、インターフェースを得る
CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC,
IID_IGraphBuilder, (void **) &pGraph);
pGraph -> QueryInterface( IID_IMediaControl, (LPVOID *) &pMC );
// キャプチャフィルタをフィルタグラフに追加
pGraph -> AddFilter( pbf, L"Video Capture");
// 追加を行ったのでキャプチャフィルタの参照をリリース
pbf -> Release();
// ---- グラバフィルタの準備 ----
// グラバフィルタを作る
CoCreateInstance( CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (LPVOID *)&pF);
pF -> QueryInterface( IID_ISampleGrabber, (void **)&pGrab );
// グラバフィルタの挿入場所の特定のための設定
ZeroMemory(&amt, sizeof(AM_MEDIA_TYPE));
amt.majortype = MEDIATYPE_Video;
amt.subtype = MEDIASUBTYPE_RGB24;
amt.formattype = FORMAT_VideoInfo;
pGrab -> SetMediaType( &amt );
// グラバフィルタをフィルタグラフに追加
pGraph -> AddFilter(pF, L"SamGra");
// ---- キャプチャグラフの準備 ----
// キャプチャグラフを作る
CoCreateInstance( CLSID_CaptureGraphBuilder2 , NULL, CLSCTX_INPROC,
IID_ICaptureGraphBuilder2, (void **) &pCapture );
// フィルタグラフをキャプチャグラフに組み込む
pCapture -> SetFiltergraph( pGraph );
// キャプチャグラフの設定、グラバをレンダリング出力に設定
pCapture -> RenderStream (&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
pbf, NULL, pF);
// ---- DVデコードの解像度をFULLに設定する ----
IEnumFilters *pEnum = NULL;
IIPDVDec *pdv = NULL;
// IBaseFilter *pbf; ULONG cFetched; 定義済み
int nnn;
FILTER_INFO FilterInfo;
char szName[256];
pGraph -> EnumFilters( &pEnum );
// 使用されるフィルタを調べる
while( pEnum -> Next( 1, &pbf, &cFetched ) == S_OK) {
pbf -> QueryFilterInfo( &FilterInfo );
WideCharToMultiByte( CP_ACP, 0, FilterInfo.achName,
-1, szName, 256, 0, 0);
printf( "使用フィルタ = %s \n", szName );
if( ! strcmp( szName, "DV Video Decoder") ){
// DVデコーダなら解像度をFULLに設定
pbf -> QueryInterface( IID_IIPDVDec, (void **)& pdv );
pdv -> get_IPDisplay( &nnn );
printf(" 既設デコード解像度 IP = %d \n",nnn);
pdv -> put_IPDisplay( 1000 );
pdv -> get_IPDisplay( &nnn );
printf(" 仮設デコード解像度 IP = %d \n",nnn);
}
FilterInfo.pGraph -> Release();
pbf -> Release();
}
pEnum -> Release();
// ---- 表示ウィンドウの準備 ----
// ビットマップ情報の取得
pGrab -> GetConnectedMediaType( &amt );
// ビデオ ヘッダーへのポインタを獲得する。
printf( "amt.lSampleSize = %d \n", amt.lSampleSize );
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)amt.pbFormat;
// ビデオ ヘッダーには、ビットマップ情報が含まれる。
// ビットマップ情報を BITMAPINFO 構造体にコピーする。
BITMAPINFO BitmapInfo;
ZeroMemory( &BitmapInfo, sizeof(BitmapInfo) );
CopyMemory( &BitmapInfo.bmiHeader, &(pVideoHeader->bmiHeader),
sizeof(BITMAPINFOHEADER));
printf( "BitmapInfo.bmiHeader.biBitCount = %d\n",
BitmapInfo.bmiHeader.biBitCount );
img00.bih = BitmapInfo.bmiHeader;
long n = img00.bih.biSizeImage;
buffer = (BYTE *)malloc( n );
img00.lpBmpData = buffer;
img00.hi = (HINSTANCE)GetWindowLong( HWND_DESKTOP, GWL_HINSTANCE );
img00.x = 100; img00.y = 100;
gr_reg(); // 表示ウィンドウの定義
printf( "キャプチャを開始します、どれかキーを押して下さい\n" );
getch();
// ---- キャプチャ開始 ----
gr_init(&img00); // 表示ウィンドウを作る
pGrab -> SetBufferSamples(TRUE); // グラブ開始
pMC -> Run(); // レンダリング開始
int nn = 0; // グラブ回数のカウント、意味はない
while(1){
hr = pGrab -> GetCurrentBuffer( &n, (long *)buffer );
// グラブ
printf("グラブ hr = %x, n = %d\n", hr, nn++ );
InvalidateRect( img00.hwnd, NULL, FALSE);
if( kbhit() ){
getch(); // kbhitで取得したキーの破棄
hr = pMC -> Pause();
printf( "グラブ停止:終了 -- q 、再開 -- その他のキー\n" );
if(getch() == 'q')
break;
pMC -> Run();
}
}
// ---- 終了処理 ----
// インターフェースのリリース
pMC -> Release();
pGraph -> Release();
pCapture -> Release();
// COMのリリース
CoUninitialize();
SendMessage( img00.hwnd, WM_CLOSE, 0, 0 ); // グラフィック画面の終了
free( buffer );
}
4.おわりに
以上簡単ですが、DirectShowを使ったビデオ周りのデータ操作やDirectShowの外観が理解できたかと思います。ここで紹介した機能はDirectShowを含めたDirectXのほんの一部にすぎません。DirectShow以外にも計測結果の表示に使えそうなDirect3Dなど計測等の実務に利用できそうなものはまだまだあります。
今回のプログラムは見通しをよくするためにエラー処理やとりあえずは必要のない設定はできるかぎり省いていて、利用するハードウェアもWindows98SE、DVキャプチャボードはWindows98SE付属のドライバでドライブできるものに限定されます。またリストA,Bともグラフィック表示部分は先月号のリストlist1.h、list1b.cppを使用しています。
図6 リストBの実行画面
表1 使用したCOMの一覧
表 1 使用した主なコンポーネント一覧 (各コンポーネントのインターフェース、メソッドの詳細はSDKを参照して下さい)
┌──────────────┬────────────────────────────────┬──────────────────────────────┐
│ クラス名(クラス識別子) │ 解説 │ インターフェイス │
├──────────────┼────────────────────────────────┼──────────────────────────────┤
│フィルタ グラフ マネージャ │フィルタ グラフを作成および制御する。フィルタ グラフ マネージャ │IAMGraphStreams、IBasicAudio、IBasicVideo、IBasicVideo2、 │
│(CLSID_FilterGraph) │は、DirectShowの中心的コンポーネントである。アプリケーションは、│IFilterChain、IFilterGraph、IFilterGraph2、IFilterMapper2、 │
│ │このコンポーネントを使用してフィルタ グラフを作成および制御す │IGraphBuilder、IGraphConfig、IGraphVersion、IMediaControl、 │
│ │る。フィルタグラフマネージャは再生すべきデータに応じたフィルタ │IMediaEvent、IMediaEventEx、IMediaEventSink、IMediaFilter、 │
│ │(これもCOM)を自動的に選択し呼び出す。 │IMediaPosition、IMediaSeeking、IQueueCommand、 │
│ │ │IResourceManager、 IVideoFrameStep、IVideoWindow │
├──────────────┼────────────────────────────────┼──────────────────────────────┤
│キャプチャ グラフ ビルダ │ ビデオ キャプチャ グラフを作成する。ビデオ キャプチャまたは │ICaptureGraphBuilder2 │
│(CLSID_CaptureGraphBuilder2)│編集アプリケーションは、このコンポーネントを使用してフィルタ │ │
│ │ グラフを作成する。 │ │
├──────────────┼────────────────────────────────┼──────────────────────────────┤
│サンプル グラバ フィルタ │サンプル グラバ フィルタは、サンプルがフィルタ グラフを通過する │ IBaseFilter、ISampleGrabber 、 │
│(CLSID_SampleGrabber) │ ときにそれを取得する方法を提供する。このフィルタは、 │ IMemInputPin、IPin、IQualityControl │
│ │ 1 つの入力ピンと 1 つの出力ピン を持つ変換フィルタである。 │ │
│ │ このフィルタは、すべてのサンプルをそのままダウンストリームに │ │
│ │ 渡すので、データ ストリームを変更せずに │ │
│ │ フィルタ グラフに挿入することができる。アプリケー │ │
│ │ションは ISampleGrabber インターフェイスでメソッドを呼び出すこと│ │
│ │で、フィルタから個々のサンプルを取得できる。 │ │
├──────────────┼────────────────────────────────┼──────────────────────────────┤
│DV ビデオデコーダフィルタ │ このフィルタは、デジタル ビデオ (DV) ストリームを非圧縮ビデオに│IBaseFilter、IIPDVDec、 IPersistStream、 │
│(CLSID_DVVideoCodec) │ デコードする。このフィルタは、デコード解像度を設定するための │ISpecifyPropertyPages、 │
│ │ カスタム インターフェイス IIPDVDec を提供する。 │IMemInputPin、IPin、IQualityControl、 │
│ │ │IMediaPosition、IMediaSeeking │
├──────────────┼────────────────────────────────┼──────────────────────────────┤
│システム デバイス列挙子 │システムにインストールされているフィルタおよびハードウェア │ICreateDevEnum │
│(CLSID_SystemDeviceEnum) │デバイスを列挙する。アプリケーションは、このコンポーネントを │ │
│ │使用して任意のカテゴリのフィルタおよびデバイスを検索することが │ │
│ │ できる。 │ │
└──────────────┴────────────────────────────────┴──────────────────────────────┘
-------以上
パソコンによる動画計測とその周辺
/ 1.動画処理のための準備 / VFWく
VFW,プログラム2