C# - OpenCvSharp을 이용한 동영상(avi, mp4, ...) 처리
OpenCvSharp을 이용해,
OpenCV 응용 프로그램을 C#으로 구현 - OpenCvSharp
; https://www.sysnet.pe.kr/2/0/11402
동영상 처리하는 것도 다음과 같이 간단합니다.
[Cpp] Capturing Video
; https://github.com/shimat/opencvsharp/wiki/%5BCpp%5D-Capturing-Video
using OpenCvSharp;
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
VideoCapture capture = new VideoCapture("c:\\temp\\test.avi");
int sleepTime = (int)Math.Round(1000 / capture.Fps);
using (Window window = new Window("capture"))
using (Mat image = new Mat()) // Frame image buffer
{
// When the movie playback reaches end, Mat.data becomes NULL.
while (true)
{
capture.Read(image); // same as cvQueryFrame
if (image.Empty())
break;
window.ShowImage(image);
Cv2.WaitKey(sleepTime);
}
}
}
}
}
참고로 Cv2.WaitKey를 Thread.Sleep으로 바꾸면 동영상 출력이 안됩니다. (아마도 Cv2.WaitKey 내부 로직에는 OpenCvSharp.Window의 메시지 처리 등도 포함이 된 것 같습니다.)
그런데, Windows Forms로 올려서 OpenCvSharp.Window 없이 처리하려면 어떻게 해야 할까요? 이를 위해 다음과 같은 식으로 처리를 해봤는데요.
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// base.OnPaintBackground(e);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
while (true)
{
if (_q.TryTake(out Bitmap item) == true)
{
g.DrawImage(item, new PointF(0, 0));
item.Dispose();
}
else
{
break;
}
}
}
BlockingCollection<Bitmap> _q = new BlockingCollection<Bitmap>();
private Thread camera;
private void CaptureCameraCallback()
{
VideoCapture capture = new VideoCapture("c:\\temp\\test.avi");
if (capture.IsOpened() == false)
{
return;
}
int fps = (int)capture.Fps;
int expectedProcessTimePerFrame = 1000 / fps;
Stopwatch st = new Stopwatch();
st.Start();
using (Mat image = new Mat())
{
while (true)
{
long started = st.ElapsedMilliseconds;
capture.Read(image);
if (image.Empty() == true)
{
break;
}
_q.Add(BitmapConverter.ToBitmap(image));
this.Invoke((Action)(() => this.Invalidate()));
int elapsed = (int)(st.ElapsedMilliseconds - started);
int delay = expectedProcessTimePerFrame - elapsed;
if (delay > 0)
{
Thread.Sleep(delay);
}
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
camera = new Thread(CaptureCameraCallback);
camera.IsBackground = true;
camera.Start();
}
}
}
이전의 OpenCvSharp.Window를 이용했던 예제와 비교해 성능이 심각하게 하락했습니다. 예를 들어 1280 * 720 해상도의 24 fps 동영상도 화면이 뚝뚝 끊기면서 나오고 CPU 사용량도 늘었습니다. 단순히 OpenCvSharp.Window를 출력할 때는 CPU가 거의 0에 가깝게 소비하고 있었는데 위와 같이 바꾸니까 항상 7 ~ 12% 이상을 소비합니다. (4Core 기준)
물론 OpenCvSharp.Window를 사용할 때와는 다르게 ToBitmap의 호출로 인한 버퍼 복사에 대한 부하가 있겠지만 가장 병목이 되는 지점은 Graphics.DrawImage였습니다. DrawImage 호출에 20ms ~ 50ms까지 부하가 나오므로 24fps를 위한 최소 기준인 41ms를 심심치 않게 초과하는 것입니다.
Graphics.DrawImage에 대해 검색해 봐도 비슷한 의견을 많이 볼 수 있습니다.
Is Graphics.DrawImage too slow for bigger images?
; https://stackoverflow.com/questions/11020710/is-graphics-drawimage-too-slow-for-bigger-images
그러니까, 혹시나 GDI로 영상 처리를 한다면 단일 프레임은 그나마 상관없겠지만 다중 프레임을 처리해야 하는 상황이라면 이런 성능 차이를 고려해 OpenCvSharp.Window로 처리하는 것이 권장됩니다.
(
첨부한 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]