F#でWPF --- CatmullRom曲線の描画
図形を描画する際に、指定した点を通る滑らかな曲線を描きたいことがあります。
今回はCatmullRom曲線でこれを実現したいと思います。
CatmullRom曲線
2点間の補間は、更にその前後の点を利用して求められます。
2点(p1、p2)と、その前後の点(p0、p3)の4点から、下記の式にて任意の位置が求められます。
Vector2定義
System.Windows.Vectorでは演算子が不十分であるため、独自のVector2を下記の様に定義しました。
type Vector2 = { X:double; Y:double } static member (~-) (v:Vector2) = { X = -v.X; Y = -v.Y } static member (+) (a:Vector2, b:Vector2) = { X = a.X + b.X; Y = a.Y + b.Y } static member (-) (a:Vector2, b:Vector2) = { X = a.X - b.X; Y = a.Y - b.Y } static member (*) (s:double, v:Vector2) = { X = s * v.X; Y = s * v.Y } static member (*) (v:Vector2, s:double) = s * v static member (/) (v:Vector2, s:double) = { X = v.X / s; Y = v.Y / s }
CatmullRom定義
Vector2 listを持つレコードと、interpolate関数を定義しています。
type CatmullRom = { Points : Vector2 list } [<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>] module CatmullRom = let interpolate (p0 : Vector2) (p1 : Vector2) (p2 : Vector2) (p3 : Vector2) t = let v0 = (p2 - p0) / 2.0 let v1 = (p3 - p1) / 2.0 (2.0 * p1 - 2.0 * p2 + v0 + v1) * t * t * t + (-3.0 * p1 + 3.0 * p2 - 2.0 * v0 - v1) * t * t + v0 * t + p1
Polylineで表示
CatmullRom曲線をPolylineで表示してみます。
4つの点の間を100分割して表示しています。(始点、終点は複製をしてあるため、入力は6点となっています。)
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="MainWindow" Height="180" Width="160"> <Canvas Margin="20"> <Polyline Stroke="Red" StrokeThickness="10" Points="{Binding Points}" /> </Canvas> </Window>
open System open System.Windows open System.Windows.Media type Model = { Points : PointCollection } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window let interpolate p0 p1 p2 p3 count = [ 0..count - 1 ] |> List.map (fun x -> double x / double count) |> List.map (CatmullRom.interpolate p0 p1 p2 p3) let points = [ { X = 0.0; Y = 0.0 } { X = 0.0; Y = 0.0 } { X = 100.0; Y = 0.0 } { X = 0.0; Y = 100.0 } { X = 100.0; Y = 100.0 } { X = 100.0; Y = 100.0 } ] |> List.windowed 4 |> List.collect (fun x -> interpolate x.[0] x.[1] x.[2] x.[3] 100) let toPoint x = Point(x.X, x.Y) window.DataContext <- { Points = points |> List.map toPoint |> PointCollection } Application().Run(window) |> ignore 0
Pathで描画
スタート位置をPathFigureで指定し、残りをPolyBezierSegmentのPointsにバインドすることによりベジエ曲線を描いています。
Pointsの中身は(p1、c1_1、c2_2、p2、c2_1、c2_2、p3...)のように、通過点の間にコントロールポイントが挟まっているものになります。
Polylineで描画したものと同じ図形であることが確認できます。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="MainWindow" Height="180" Width="160"> <Canvas Margin="20"> <Path Stroke="Red" StrokeThickness="10"> <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigure StartPoint="{Binding BezierStartPoint}"> <PolyBezierSegment Points="{Binding BezierPoints}" /> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path> </Canvas> </Window>
module CatmullRom = let bezierControlPoints (p0 : Vector2) (p1 : Vector2) (p2 : Vector2) (p3 : Vector2) = let c1 = p1 + (p2 - p0) / 6.0 let c2 = p2 + (p1 - p3) / 6.0 c1, c2 type Model = { BezierStartPoint : Point BezierPoints : PointCollection } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window let points = [ { X = 0.0; Y = 0.0 } { X = 0.0; Y = 0.0 } { X = 100.0; Y = 0.0 } { X = 0.0; Y = 100.0 } { X = 100.0; Y = 100.0 } { X = 100.0; Y = 100.0 } ] |> List.windowed 4 |> List.collect (fun x -> let c1, c2 = CatmullRom.bezierControlPoints x.[0] x.[1] x.[2] x.[3] [ c1; c2; x.[2] ]) let toPoint x = Point(x.X, x.Y) window.DataContext <- { BezierStartPoint = Point(0.0, 0.0) BezierPoints = points |> List.map toPoint |> PointCollection } Application().Run(window) |> ignore 0
MediaFoundation --- 音声読み書き
下記記事でMediaFoundationを用いて動画の読み書きを行いました。
MediaFoundation --- 動画の読み込み - 何でもプログラミング
MediaFoundation --- 動画書き出し - 何でもプログラミング
今回は音声データの読み書きを行ってみたいと思います。
分かりやすさ重視のため、エラー処理は省いてあります。
アプリケーションコード
今回はOpenCVのサンプルデータ、Megamind.aviから音声データだけ抽出します。
音声データはAAC形式で保存してみます。
出力bytesPerSecは12000、16000、20000、24000のいずれかとなります。(詳しくは下記を参照してください)
AAC Encoder (Windows)
ConfigureAudioDecoder、GetAudioInfo、ConfigureAudioEncoder、CaptureAudio、WriteAudioは後述いたします。
#include <mfapi.h> #include <mfidl.h> #include <mfreadwrite.h> #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "mfreadwrite.lib") int main() { CoInitialize(NULL); MFStartup(MF_VERSION); // Reader作成 IMFSourceReader* reader; MFCreateSourceReaderFromURL(L"c:\\lib\\opencv3.2\\sources\\samples\\data\\megamind.avi", NULL, &reader); ConfigureAudioDecoder(reader, MFAudioFormat_PCM); UINT32 channels, samplePerSec, bitsPerSample, bytesPerSec; GetAudioInfo(reader, &channels, &samplePerSec, &bitsPerSample, &bytesPerSec); // Writer作成 IMFSinkWriter* writer; MFCreateSinkWriterFromURL(L"output.mp4", NULL, NULL, &writer); auto streamIndex = ConfigureAudioEncoder(writer, MFAudioFormat_PCM, MFAudioFormat_AAC, channels, samplePerSec, bitsPerSample, 24000); // 書き出し writer->BeginWriting(); while (true) { LONGLONG time, duration; auto data = CaptureAudio(reader, &time, &duration); if (data.empty()) break; WriteAudio(writer, streamIndex, data.data(), data.size(), time, duration); } writer->Finalize(); MFShutdown(); CoUninitialize(); return 0; }
ConfigureAudioDecoder
デコード先のフォーマットを指定して、readerにセットします。
void ConfigureAudioDecoder(IMFSourceReader* reader, GUID format) { IMFMediaType* mediaType; MFCreateMediaType(&mediaType); mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); mediaType->SetGUID(MF_MT_SUBTYPE, format); reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, mediaType); mediaType->Release(); }
GetAudioInfo
チャンネル数、サンプリング数、サンプリングサイズ、ビットレートを取得できます。
void GetAudioInfo(IMFSourceReader* reader, UINT32* channels, UINT32* samplePerSec, UINT32* bitsPerSample, UINT32* bytesPerSec)
{
IMFMediaType* mediaType;
reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &mediaType);
mediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels);
mediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplePerSec);
mediaType->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample);
mediaType->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerSec);
}
ConfigureAudioEncoder
入力フォーマット、エンコーダ、チャンネル数、サンプリング数、サンプリングサイズ、ビットレートを指定します。
DWORD ConfigureAudioEncoder(IMFSinkWriter* writer, GUID inputFormat, GUID outputFormat, UINT32 channels, UINT32 samplePerSec, UINT32 bitsPerSample, UINT32 bytesPerSec) { auto createType = [&](GUID subtype) { IMFMediaType* type; MFCreateMediaType(&type); type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); type->SetGUID(MF_MT_SUBTYPE, subtype); type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels); type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplePerSec); type->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample); type->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerSec); return type; }; // OutputType IMFMediaType* outputType = createType(outputFormat); DWORD streamIndex; writer->AddStream(outputType, &streamIndex); outputType->Release(); // InputType IMFMediaType* inputType = createType(inputFormat); writer->SetInputMediaType(streamIndex, inputType, NULL); inputType->Release(); return streamIndex; }
CaptureAudio
1フレーム当たりの音声データを取得します。
ついでにフレームの開始時間と長さも取得します。
std::vector<BYTE> CaptureAudio(IMFSourceReader* reader, LONGLONG* time, LONGLONG* duration) { // 読み込み DWORD flags; IMFSample* sample; reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &flags, NULL, &sample); if (sample == nullptr) return std::vector<BYTE>(); // Buffer取得 IMFMediaBuffer* buffer; sample->GetBufferByIndex(0, &buffer); // データ取得 BYTE* p; DWORD size; buffer->Lock(&p, NULL, &size); std::vector<BYTE> data(size); memcpy(data.data(), p, size); buffer->Unlock(); // 開始時間、長さ取得 sample->GetSampleTime(time); sample->GetSampleDuration(duration); buffer->Release(); sample->Release(); return data; }
WriteAudio
音声データをwriterで書き出します。
SetSampleTime、SetSampleDurationを行わないとエラーが出力されます。
void WriteAudio(IMFSinkWriter* writer, int streamIndex, BYTE* data, int size, LONGLONG time, LONGLONG duration) { // Buffer作成 IMFMediaBuffer* buffer; MFCreateMemoryBuffer(size, &buffer); // データ書き込み BYTE* bufferPtr; buffer->Lock(&bufferPtr, NULL, NULL); memcpy(bufferPtr, data, size); buffer->Unlock(); buffer->SetCurrentLength(size); // Sample作成 IMFSample* sample; MFCreateSample(&sample); sample->AddBuffer(buffer); sample->SetSampleTime(time); sample->SetSampleDuration(duration); // 書き出し writer->WriteSample(streamIndex, sample); sample->Release(); buffer->Release(); }
MediaFoundation --- 動画書き出し
下記記事にてMediaFoundationで動画を読み込む方法を調べました。
MediaFoundation --- 動画の読み込み - 何でもプログラミング
今回は書き出しについて調べたいと思います。
尚、見通し優先のため、エラー処理は省いてあります。
アプリケーションコード
動画はOpenCVのサンプル、vtest.aviを利用します。
動画を読み込んでH.264で出力します。
動画の読み込みにはOpenCVを利用しています。
ConfigureVideoEncoder、Writeは後述いたします。
#include <mfapi.h> #include <mfidl.h> #include <mfreadwrite.h> #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "mfreadwrite.lib") #include <opencv2\opencv.hpp> #pragma comment(lib, "opencv_world320d.lib") int main() { CoInitialize(NULL); MFStartup(MF_VERSION); // 動画読み込み cv::VideoCapture capture("c:\\lib\\opencv3.2\\sources\\samples\\data\\vtest.avi"); int width = (int)capture.get(CV_CAP_PROP_FRAME_WIDTH); int height = (int)capture.get(CV_CAP_PROP_FRAME_HEIGHT); double fps = capture.get(CV_CAP_PROP_FPS); // Writer準備 IMFSinkWriter* writer; MFCreateSinkWriterFromURL(L"output.mp4", NULL, NULL, &writer); int streamIndex = ConfigureVideoEncoder(writer, MFVideoFormat_RGB24, MFVideoFormat_H264, width, height, fps, width * height); // 書き出し writer->BeginWriting(); while (true) { cv::Mat frame; capture >> frame; if (frame.empty()) break; cv::flip(frame, frame, 0); // H264で出力する際、上下反転が必要 Write(writer, streamIndex, frame.data, frame.elemSize() * frame.total()); } writer->Finalize(); // 後処理 writer->Release(); MFShutdown(); CoUninitialize(); return 0; }
ConfigureVideoEncoder
入力フォーマット、エンコーダ、画像サイズ、fps、平均bitrate、以上を渡してエンコーダをセットアップします。
DWORD ConfigureVideoEncoder(IMFSinkWriter* writer, GUID inputFormat, GUID outputFormat, int width, int height, double fps, int bitrate) { auto createType = [&](GUID subtype) { IMFMediaType* type; MFCreateMediaType(&type); type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); type->SetGUID(MF_MT_SUBTYPE, subtype); type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); MFSetAttributeSize(type, MF_MT_FRAME_SIZE, width, height); MFSetAttributeRatio(type, MF_MT_FRAME_RATE, (int)(fps * 10000000), 10000000); return type; }; // OutputType IMFMediaType* outputType = createType(outputFormat); DWORD streamIndex; outputType->SetUINT32(MF_MT_AVG_BITRATE, bitrate); writer->AddStream(outputType, &streamIndex); outputType->Release(); // InputType IMFMediaType* inputType = createType(inputFormat); writer->SetInputMediaType(streamIndex, inputType, NULL); inputType->Release(); return streamIndex; }
Write
WMVで出力したい場合は、IMFSampleのSetSampleTimeとSetSampleDurationを設定する必要があります。
void Write(IMFSinkWriter* writer, int streamIndex, BYTE* data, int size) { // buffer作成 IMFMediaBuffer* buffer; MFCreateMemoryBuffer(size, &buffer); BYTE* bufferPtr; buffer->Lock(&bufferPtr, NULL, NULL); memcpy(bufferPtr, data, size); buffer->Unlock(); buffer->SetCurrentLength(size); // Sample作成 IMFSample* sample; MFCreateSample(&sample); sample->AddBuffer(buffer); // 書き出し writer->WriteSample(streamIndex, sample); sample->Release(); buffer->Release(); }
MediaFoundation --- 動画の読み込み
下記記事にてOpenCVで動画を読み書きする方法を調べました。
OpenCVで動画読み込み&書き込み - 何でもプログラミング
今回はWindows特有のMediaFoundationを利用して動画を読み込んでみます。
全体の流れの把握重視の為、エラー処理は全て省いてあります。
アプリケーションコード
動画を読み込んで、そのまま書き出しています。
今回は書き出しにはOpenCVを利用しました。
IMFSourceReaderを作成、デコーダ設定、読み込み、の流れになります。
動画はOpenCVのサンプルである、vtest.aviを利用しています。
MediaFoundationを利用するにあたり、MFStartupを忘れないようにしてください。(CoInitializeも、されていなければ呼び出してください。)
ConfigureVideoDecoder、GetVideoInfo、Captureは後程記載します。
#include <mfapi.h> #include <mfidl.h> #include <mfreadwrite.h> #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "mfreadwrite.lib") #include <opencv2\opencv.hpp> #pragma comment(lib, "opencv_world320.lib") int main() { CoInitialize(NULL); MFStartup(MF_VERSION); IMFSourceReader* reader; MFCreateSourceReaderFromURL(L"c:\\lib\\opencv3.2\\sources\\samples\\data\\vtest.avi", NULL, &reader); ConfigureVideoDecoder(reader, MFVideoFormat_RGB24); UINT32 width, height; double fps; GetVideoInfo(reader, &width, &height, &fps); auto writer = cv::VideoWriter("output.avi", cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), fps, cv::Size(width, height)); while (true) { auto data = Capture(reader); if (data.empty()) break; cv::Mat frame(height, width, CV_8UC3, data.data()); writer << frame; } reader->Release(); MFShutdown(); CoUninitialize(); return 0; }
ConfigureVideoDecoder
SourceReaderにどのフォーマットで出力するか設定します。
上記ではMFVideoFormat_RGB24を指定していますが、デコーダによってはMFVideoFormat_YUY2しか出力しない等ありますので、利用の際は注意してください。
対応出力は下記ページのVideo Codecs、Decoderのリンク先を参照してください。
Supported Media Formats in Media Foundation (Windows)
void ConfigureVideoDecoder(IMFSourceReader* reader, GUID format) { IMFMediaType* mediaType; MFCreateMediaType(&mediaType); mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); mediaType->SetGUID(MF_MT_SUBTYPE, format); reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, mediaType); mediaType->Release(); }
GetVideoInfo
SourceReaderからサイズとfpsを取得しています。
void GetVideoInfo(IMFSourceReader* reader, UINT32* width, UINT32* height, double* fps) { IMFMediaType* mediaType; reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediaType); MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, width, height); UINT32 nume, denom; MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &nume, &denom); *fps = (double)nume / denom; mediaType->Release(); }
Capture
ReadSampleにてIMFSampleを取得し、そこからIMFMediaBufferを取得、データ取り出しを行っています。
ReadSample時にflagsを渡さないとsampleがnullptrになってしまいます。
std::vector<BYTE> Capture(IMFSourceReader* reader) { DWORD flags; IMFSample* sample; reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &flags, NULL, &sample); if (sample == nullptr) return std::vector<BYTE>(); IMFMediaBuffer* buffer; sample->GetBufferByIndex(0, &buffer); BYTE* p; DWORD size; buffer->Lock(&p, NULL, &size); std::vector<BYTE> data(size); memcpy(data.data(), p, size); buffer->Unlock(); buffer->Release(); sample->Release(); return data; }
シーク
SourceReaderにはSetCurrentPositionがあり、100ns単位で指定します。
ただしその位置に一番近いキーフレームに移動します。
void SeekToKeyframe(IMFSourceReader* reader, int _100ns) { PROPVARIANT var; InitPropVariantFromInt64(_100ns, &var); auto hr = reader->SetCurrentPosition(GUID_NULL, var); PropVariantClear(&var); }
動画時間取得
durationは100ns単位の値になります。
PROPVARIANT var; reader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var); LONGLONG duration; PropVariantToInt64(var, &duration); PropVariantClear(&var);
Fourcc取得
IMFMediaType* mediaType; reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &mediaType); GUID subtype; mediaType->GetGUID(MF_MT_SUBTYPE, &subtype); char fourcc[] = { (char) (subtype.Data1 & 0XFF), (char)((subtype.Data1 & 0XFF00) >> 8), (char)((subtype.Data1 & 0XFF0000) >> 16), (char)((subtype.Data1 & 0XFF000000) >> 24), };
OpenCVで動画読み込み&書き込み --- C++/CLIでラップ
下記記事にてOpenCVで動画を読み書きする方法を調べました。
OpenCVで動画読み込み&書き込み - 何でもプログラミング
今回は.Netで利用できるよう、C++/CLIでラップしてみたいと思います。
尚、Matクラス(Mat8UC1クラス)は下記記事のものを利用しています。
OpenCVをC++/CLIでラップ - 何でもプログラミング
VideoCapture
FourccはStringで返すようにしてみました。
Mat8UC1を利用しているため、Captureの中で8UC1に変換しています。ここは適宜変更してください。
using namespace msclr::interop; using namespace System; public ref class VideoCapture { public: property int Width { int get() { return (int)_capture->get(CV_CAP_PROP_FRAME_WIDTH); } } property int Height { int get() { return (int)_capture->get(CV_CAP_PROP_FRAME_HEIGHT); } } property double Fps { double get() { return _capture->get(CV_CAP_PROP_FPS); } } property int Count { int get() { return (int)_capture->get(CV_CAP_PROP_FRAME_COUNT); } } property String^ Fourcc { String^ get() { int i = (int)_capture->get(CV_CAP_PROP_FOURCC); char fourcc[] = { (char)(i & 0XFF), (char)((i & 0XFF00) >> 8), (char)((i & 0XFF0000) >> 16), (char)((i & 0XFF000000) >> 24), 0 }; return gcnew String(fourcc); } } property bool IsOpened { bool get() { return _capture->isOpened(); } } VideoCapture(String^ path) { _capture = new cv::VideoCapture(marshal_as<std::string>(path)); } !VideoCapture() { if (_capture != nullptr) { delete _capture; _capture = nullptr; } } ~VideoCapture() { this->!VideoCapture(); } Mat8UC1^ Capture() { cv::Mat src; *_capture >> src; auto dst = gcnew Mat8UC1(src.rows, src.cols); if (src.channels() == 1) src.copyTo(*dst->_mat); else if (src.channels() == 3 || src.channels() == 4) cv::cvtColor(src, *dst->_mat, CV_RGB2GRAY); return dst; } void Seek(int frame) { _capture->set(CV_CAP_PROP_POS_FRAMES, frame); } private: cv::VideoCapture* _capture; };
VideoWriter
Fourcc文字列からIntに変換するにはcv::VideoWriter::fourccを利用します。
Write関数内でカラー化していますが、ここは適宜変更してください。
public ref class VideoWriter { public: property bool IsOpened { bool get() { return _writer->isOpened(); } } VideoWriter(String^ path, int width, int height, String^ fourcc, double fps) { _writer = new cv::VideoWriter( marshal_as<std::string>(path), cv::VideoWriter::fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]), fps, cv::Size(width, height)); } !VideoWriter() { if (_writer != nullptr) { delete _writer; _writer = nullptr; } } ~VideoWriter() { this->!VideoWriter(); } void Write(Mat8UC1^ mat) { cv::Mat color; cv::cvtColor(*mat->_mat, color, CV_GRAY2BGR); *_writer << color; } private: cv::VideoWriter* _writer; };
F#で利用
動画はopenCVのサンプルMegamind.aviを利用しています。
動画をそのまま保存しているだけですが、Mat8UC1を介しているため出力はグレースケールになっています。
[<EntryPoint>] let main argv = let capture = new CV.VideoCapture(@"C:\lib\opencv3.2\sources\samples\data\megamind.avi") let writer = new CV.VideoWriter(@"output.avi", capture.Width, capture.Height, capture.Fourcc, capture.Fps); capture |> Seq.unfold (fun x -> Some(x.Capture(), x)) |> Seq.takeWhile (fun x -> not x.IsEmpty) |> Seq.iter writer.Write 0
OpenCVで動画読み込み&書き込み
OpenCVのダウンロードは下記記事を参照してください。
OpenCVをC++/CLIでラップ - 何でもプログラミング
VideoCapture
動画を読み込むにはVideoCaptureクラスを利用します。
パスを指定して開き、>>オペレータでフレームを取得します。
cv::VideoCapture capture("c:\\..."); cv::Mat frame; capture >> frame;
VideoWriter
動画を出力するにはVideoWriterクラスを利用します。
パス、コーデック、fps、サイズを指定して作成し、<<オペレータでフレームを出力します。
cv::VideoWriter writer("c:\\...", fourcc, fps, cv::Size(width, height)); writer << frame;
エッジ化して出力する例
Win32コンソールアプリケーション(64bit)を想定しています。
動画は、opencvのサンプルとして付属しているMegamind.aviを利用します。(sources/samples/data/Megamind.avi)
開いた動画の情報は、VideoCaptureのget関数で色々取得できます。
作成したexeと同じフォルダにopencv_world320.dllとopencv_ffmpeg320_64.dllをコピーするのを忘れないようにしてください。
#include <opencv2\opencv.hpp> #pragma comment(lib, "opencv_world320.lib") int main() { cv::VideoCapture capture("c:\\lib\\opencv3.2\\sources\\samples\\data\\megamind.avi"); int width = (int)capture.get(CV_CAP_PROP_FRAME_WIDTH); int height = (int)capture.get(CV_CAP_PROP_FRAME_HEIGHT); int count = (int)capture.get(CV_CAP_PROP_FRAME_COUNT); int fourcc = (int)capture.get(CV_CAP_PROP_FOURCC); double fps = capture.get(CV_CAP_PROP_FPS); cv::VideoWriter writer("out.avi", fourcc, fps, cv::Size(width, height), false); while (true) { cv::Mat frame; capture >> frame; if (frame.empty()) break; cv::Mat edge; cv::Canny(frame, edge, 50, 150); writer << edge; } return 0; }
シーク
CV_CAP_PROP_POS_FRAMESでフレーム位置を指定できます。(その他ミリ秒で設定できるCV_CAP_PROP_POS_MSECなどもあります)
capture.set(CV_CAP_PROP_POS_FRAMES, 100);
H.264
上記のアプリケーションでは、H.264の読込は出来ても、書き込みはできません。
書き込みをしたい場合は、下記からopenh264-1.6.0-win64msvc.dllをダウンロードしてexeと同じフォルダに配置してください。
Releases · cisco/openh264 · GitHub
F#でWPF --- BitmapImageで画像ロード
下記記事にてOpenCVと連携して画像を表示しました。
画像を読み込むだけであればWPFの標準セットで可能であるため、今回はWPFで画像をロードして表示してみます。
ついでにグレースケール化も行ってみます。
作成するアプリケーション
ファイルダイアログで画像を選び、表示するアプリケーションを作成します。
アプリケーションコード
読込にはBitmapImageを利用します。(読込失敗の場合は例外が発生します。)
グレースケール化は、FormatConvertedBitmapを利用します。
その他ColorConvertedBitmap、TransformedBitmapなど色々あります。
Xamlは上記記事のものをそのまま使います。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:Actions;assembly=OpenCVSample" Title="MainWindow" Height="250" Width="400"> <Grid> <Button Content="画像を開く" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:OpenFileDialogAction Command="{Binding LoadImage}" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> <UniformGrid Margin="10,40,10,10" Columns="2"> <Image Source="{Binding SrcImage}" /> <Image Source="{Binding DstImage}" /> </UniformGrid> </Grid> </Window>
F#もupdateModel以外は上記記事のものをそのまま使います。
open System open System.Windows open System.Windows.Media open System.Windows.Media.Imaging type Model = { SrcImage : BitmapSource DstImage : BitmapSource } let initialModel = { SrcImage = null DstImage = null } type Msg = LoadImage of string let updateModel model msg = match msg with | LoadImage x -> let bmp = try BitmapImage(Uri(x)) with | _ -> failwith "not supported" let gray = FormatConvertedBitmap(bmp, PixelFormats.Gray8, null, 0.0) { model with SrcImage = bmp DstImage = gray } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext(initialModel, updateModel, id) Application().Run(window) |> ignore 0