//////////////////////////////////////////////////// //4Kカメラの映像を(好きな解像度で)取り込むプログラム. //世の中の偉い人達は関数で小分けにしますが, //ここでは理屈を順に追いやすいようすべて書き下しています. //あと最初にusing namespace Gdiplus;と入れると最後の画像処理のGdiplus::も不要になりますが, //何がGdiplusの仕事なのか判るようにあえて全部記述しています. //////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #pragma comment(lib, "gdiplus.lib") #pragma comment(lib,"DXVA2.lib") #pragma comment(lib,"EVR.lib") #pragma comment(lib,"MF.lib") #pragma comment(lib,"MFPLAT.lib") #pragma comment(lib,"MFPLAY.lib") #pragma comment(lib,"MFUUID.lib") #pragma comment(lib,"MFREADWRITE.lib") #define RANGE_CHECK(x,min,max) ((x= (x void SafeRelease(T **pp) { if(*pp) { (*pp)->Release(); *pp = NULL; } } int main(int, char**) { HRESULT hr; ///////////////////////////// //カメラのアクティブ化を行う ///////////////////////////// IMFMediaSource *source; IMFAttributes *attr; IMFActivate **devices; UINT32 i, count; UINT32 cchLength = 0; WCHAR *pString = NULL; WCHAR CamName[256]; source = NULL; attr = NULL; devices = NULL; count = 0; //メディアファウンデーションを使えるようにする. hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); hr = MFStartup(MF_VERSION); //属性ストアを作成.カメラを1個のみ使うのであれば1で良い. hr = MFCreateAttributes(&attr, 1); if(FAILED(hr)) return 0; //GUIDにSOURCE_TYPEがVIDCAPであると指定 hr = attr->SetGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); if(FAILED(hr)) return 0; //ビデオカメラの個数を得る hr = MFEnumDeviceSources(attr, &devices, &count); if(FAILED(hr) || count == 0) return 0; if(count == 0) return 0; printf("使えるカメラの個数は%d.\nとりあえずこのサンプルではカメラ番号0を使います.\n",count); //////////////////////////////////////////////////////// //以下 device[]の[]内の数値を使いたいカメラの番号にする //////////////////////////////////////////////////////// //カメラ名の長さを取得 hr = devices[0]->GetStringLength(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &cchLength); if(FAILED(hr)) return 0; //カメラ名を取得し表示 hr = devices[0]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, CamName, cchLength + 1, NULL); if(FAILED(hr)) return 0; wprintf(L"カメラName:%ls\n",CamName); //カメラをアクティブ化 hr = devices[0]->ActivateObject(__uuidof(IMFMediaSource), (void **)&source); if(FAILED(hr)) return 0; //ここまででsourceにカメラにアクセスするためのアドレスが格納される.以降はsourceを使うので,その他のクラスのメモリを解放 SafeRelease(&attr); SafeRelease(&devices[0]); CoTaskMemFree(devices); ////////////////////////// //メディアの種類を得る ////////////////////////// BOOL selected; IMFMediaType *type; IMFPresentationDescriptor *presDesc = NULL; IMFStreamDescriptor *strmDesc = NULL; IMFMediaTypeHandler *handler = NULL; DWORD mediatypecount; char image_format[256]; UINT32 image_width = 0; UINT32 image_height = 0; UINT32 image_stride = 0; UINT32 image_length = 0; UINT32 image_fpsu = 0; UINT32 image_fpsl = 0; type = NULL; PROPVARIANT var; //様々なプロパティをやり取りするための変数の集合体である構造体PROPVARIANTを初期化 PropVariantInit(&var); //カメラと情報をやり取りするためのプレゼンテーション記述子(presDesc)へのポインターを受け取る hr = source->CreatePresentationDescriptor(&presDesc); if(FAILED(hr)) return 0; //ストリームが現在選択されている場合&selectedが TRUEで,その場合IMFMediaSource::Start が呼び出されると、メディア ソースはそのストリームのデータを生成 hr = presDesc->GetStreamDescriptorByIndex(0, &selected, &strmDesc); if(FAILED(hr)) return 0; //カメラが扱うメディア形式の情報の書いてあるハンドラーのアドレスを取得.以降handlerから使えるメディアの情報を取得できる. hr = strmDesc->GetMediaTypeHandler(&handler); if(FAILED(hr)) return 0; //カメラが扱えるビデオ形式の数を表示 hr = handler->GetMediaTypeCount(&mediatypecount); printf("カメラの使えるメディアの種類の数 = %d\n",(int)mediatypecount); //上で得たmediatypecount個のデバイスが扱えるメディアの形式を列挙.詳しくは数行下のprintfを参照. for (int typeIndex = 0; typeIndex < mediatypecount; typeIndex++){ handler->GetMediaTypeByIndex(typeIndex, &type); hr = type->GetItem(MF_MT_SUBTYPE, &var); //*var.puuidがフォーマットの種類を取り込んだヘッダファイル内で定義された数値で表している. strcpy(image_format,"しらんがな"); if(*var.puuid == MFVideoFormat_NV12) strcpy(image_format,"VFT_NV12"); if(*var.puuid == MFVideoFormat_YUY2) strcpy(image_format,"VFT_YUY2"); hr = type->GetItem(MF_MT_FRAME_SIZE, &var); Unpack2UINT32AsUINT64(var.uhVal.QuadPart,&image_width, &image_height); //var.uhVal.QuadPartに64bitデータとしてまとめて入っている物を上下32bitずつに分割 hr = type->GetItem(MF_MT_FRAME_RATE, &var); Unpack2UINT32AsUINT64(var.uhVal.QuadPart,&image_fpsl, &image_fpsu); printf("TypeIndex=%d, width=%d, height=%d,Format=%s, fps=%d/%d\n",typeIndex, image_width, image_height, image_format, image_fpsu,image_fpsl); } //上記リストで表示されるメディアの番号で使いたい番号(ここでは当研究室のカメラがNV12形式で4Kの番号24)を代入すると,今後カメラはそのメディア形式で情報を送ってくるようになる. hr = handler->GetMediaTypeByIndex(24, &type); hr = handler->SetCurrentMediaType(type); if(FAILED(hr)) return 0; SafeRelease(&presDesc); SafeRelease(&strmDesc); SafeRelease(&handler); //画像サイズを取得 hr = type->GetItem(MF_MT_FRAME_SIZE, &var); if(FAILED(hr)) return 0; Unpack2UINT32AsUINT64(var.uhVal.QuadPart,&image_width, &image_height); //画像のフォーマット名を得る(再確認)これを最後にやることで*var.puidで画像のフォーマット情報を残して後で画像処理に使う. hr = type->GetItem(MF_MT_SUBTYPE, &var); if(FAILED(hr)) return 0; if(*var.puuid != MFVideoFormat_NV12 && *var.puuid != MFVideoFormat_YUY2) return 0; if(*var.puuid == MFVideoFormat_NV12) strcpy(image_format,"VFT_NV12"); if(*var.puuid == MFVideoFormat_YUY2) strcpy(image_format,"VFT_YUY2"); printf("使用するFormat type = %s\n",image_format); printf("使用するFrame size = %d x %d\n", image_width, image_height); /* //stride値は横一列の長さ(横幅が半端な時には追加されてキリの良い4の倍数になる)これはオプションで情報が無いこともある. hr = type->GetItem(MF_MT_DEFAULT_STRIDE, &var); image_stride = var.ulVal; if(SUCCEEDED(hr)) printf(" Stride = %d\n", image_stride); //一フレームの使用情報量を取得.1フレームのサイズが決まったフォーマットでは情報がないこともある. //PropVariantInit(&var); hr = type->GetItem(MF_MT_SAMPLE_SIZE, &var); image_length = var.ulVal; if(SUCCEEDED(hr)) printf(" Sample size = %d\n", image_length); */ //////////////////////////////////////////////////////////////// //ここで初めて画像情報を読み込むためのクラスへのアドレスを取得 //////////////////////////////////////////////////////////////// IMFSourceReader *reader; hr = MFCreateSourceReaderFromMediaSource(source, NULL, &reader); if(FAILED(hr)) return 0; IMFSample *sample; DWORD streamIndex, flags; LONGLONG timeStamp; //1度目のReadSampleでは画像情報が得られないので画像情報(sample)が得られるまで繰り返す. do{ hr = reader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &streamIndex, &flags, &timeStamp, &sample); }while(!sample); IMFMediaBuffer *buff; DWORD maxLen, curLen; BYTE *memory; hr = sample->ConvertToContiguousBuffer(&buff); hr = buff->Lock(&memory, &maxLen, &curLen); //画像1フレームのメモリの長さ等も取得できるが今回はNV12やYUY2の固定長なので既知の物として使わない. //////////////////////////////////////// //ビデオフォーマットからRGBの画像に変換 //////////////////////////////////////// //画像のメモリ利用の仕様上,横幅が4の倍数でない場合は空の使用メモリが追加されて幅が4の倍数になる.これをSTRIDEと呼ぶ. const int STRIDE_UNIT_BYTE = 4; image_stride = (((image_width * 3 + STRIDE_UNIT_BYTE - 1) / STRIDE_UNIT_BYTE) * STRIDE_UNIT_BYTE); byte *CapMem=new BYTE[image_stride * image_height]; unsigned char YData,UData,VData; int PData; for (int y = 0; y < image_height; y++) { for (int x = 0; x < image_width; x++) { int i = (x * 3) + (y * image_stride); //画像用メモリの何処に書き込むか //NV12とYUY2で復元方法を変えている.YDataだけ使えばグレースケールも可能.本当はfor文の中にここのifを入れるのは処理が遅くなるので分けた方が良い. if(*var.puuid == MFVideoFormat_NV12){ YData=*(memory + (x + y * image_width)); /キャプチャメモリの先頭から画像のピクセル分だけ輝度値(0-255)が入ってる. UData=*(memory + image_width * image_height + ((int)(x/2)*2+(int)(y/2) * image_width) ); //輝度値情報の後に2ピクセルおきに2x2ピクセル分のU値(0-255) VData=*(memory + image_width * image_height + ((int)(x/2)*2+(int)(y/2) * image_width) +1); //U値の1個後ろに2x2ピクセル分のV値(0-255) } if(*var.puuid == MFVideoFormat_YUY2){ YData=*(memory + (x * 2 + (image_height-y) * image_width * 2)); //キャプチャメモリの先頭から画像のピクセル分だけ輝度値(0-255)が入ってる. UData=*(memory + ((int)(x/2) * 4 + (image_height-y) * image_width * 2) + 1); //輝度値情報の後に2ピクセルおきに2x2ピクセル分のU値(0-255) VData=*(memory + ((int)(x/2) * 4 + (image_height-y) * image_width * 2) + 3); //U値の1個後ろに2x2ピクセル分のV値(0-255) } //Rの輝度 intelなら1行目ので良いがAMDのCPUだと小数点と整数が混ざると時間がかかるので,2行目の処理を行う. //PData = YData +1.402*(VData-128); PData = ((YData<<10) +1435*(VData-128)) >> 10; RANGE_CHECK(PData,0,255); CapMem[i+2]=PData; //Gの輝度 //PData = YData -0.344*(UData-128) -0.714*(VData-128); PData = ((YData<<10) -352*(UData-128) -731*(VData-128))>>10; RANGE_CHECK(PData,0,255); CapMem[i+1]=PData; //Bの輝度 //PData = YData +1.772*(UData-128); PData = ((YData<<10) +1814*(UData-128))>>10; RANGE_CHECK(PData,0,255); CapMem[i+0]=PData; } } ////////////////////////////////////////////// //ここから画像の保存GDI+を使います. ////////////////////////////////////////////// Gdiplus::GdiplusStartupInput input; ULONG_PTR token; Gdiplus::GdiplusStartup(&token, &input, NULL); CLSID id; UINT num = 0; UINT size = 0; Gdiplus::GetImageEncodersSize(&num, &size); Gdiplus::ImageCodecInfo *pImageCodecInfo = new Gdiplus::ImageCodecInfo[size]; Gdiplus::GetImageEncoders(num, size, pImageCodecInfo); for (UINT j = 0; j < num; ++j) { if (wcscmp(pImageCodecInfo[j].MimeType, L"image/jpeg") ==0) { //保存する画像の形式を選択pngやbmp等も可能 id = pImageCodecInfo[j].Clsid; break; } } delete [] pImageCodecInfo; //圧縮率設定jpg用 ULONG quality=95; Gdiplus::EncoderParameters encoderParameters; encoderParameters.Count = 1; encoderParameters.Parameter[0].Guid = Gdiplus::EncoderQuality; encoderParameters.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong; encoderParameters.Parameter[0].NumberOfValues = 1; encoderParameters.Parameter[0].Value = &quality; //メモリ上のデータからビットマップ画像を生成 Gdiplus::Bitmap* image; image = new Gdiplus::Bitmap(image_width, image_height, image_stride, PixelFormat24bppRGB, CapMem); //Bitmap.saveはファイル名がワイド型でないといけないのでLを付ける.char[]型で名前を付けてるときはMultiByteToWideCharで変換すること. image->Save(L"Sample_out_HD.jpg", &id, &encoderParameters); //PNGやbmpの時はencoderParametersの替わりにNULLを指定. printf("24bitカラーで保存しました\n"); delete image; delete &encoderParameters; // 使ったGDI+の解放 Gdiplus::GdiplusShutdown(token); //画像メモリの解放 delete [] CapMem; hr = buff->Unlock(); SafeRelease(&buff); SafeRelease(&sample); SafeRelease(&reader); SafeRelease(&type); SafeRelease(&source); return 0; }