C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)
지난번 글의 구현을,
C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV
; https://www.sysnet.pe.kr/2/0/11407
혹시 좀 더 부하를 적게 할 수는 없을까요?
가령, copyFrameBuffer에서 화면 캡처 데이터를 복사하지 말고 그 원본 데이터로부터 곧바로 OpenCV Mat과 연결해 데이터 조작을 하는 것도 가능합니다. 이를 위해 다음과 같은 변경을 하면 됩니다.
unsafe void copyFrameBuffer(IntPtr srcPtr, IntPtr dstPtr, int srcPitch, Rectangle offsetBounds)
{
Mat mat = new Mat(new int[] { offsetBounds.Height, offsetBounds.Width }, MatType.CV_8UC4, srcPtr);
using (Mat gray = mat.CvtColor(ColorConversionCodes.BGRA2GRAY))
using (Mat blur = gray.GaussianBlur(new OpenCvSharp.Size(7, 7), 1.5, 1.5))
using (Mat canny = blur.Canny(0, 30, 3))
{
IntPtr srcMatPtr = canny.Data;
for (int y = 0; y < _renderTarget.Height; y++)
{
Utilities.CopyMemory(dstPtr + (offsetBounds.Left), srcMatPtr, _renderTarget.Width);
srcMatPtr = IntPtr.Add(srcMatPtr, _renderTarget.Width);
dstPtr = IntPtr.Add(dstPtr, _renderTarget.Width);
}
}
}
결과적으로 마지막에 복사되는 데이터도 32bit BGRA가 아닌 8bit GRAY이기 때문에 1920 * 1080인 1/4로 복사량이 줄어들게 됩니다. 따라서 SharpDX 측에서는 흑백 데이터를 다시 BGRA로 복원만 하고 화면에 출력하면 됩니다.
private void CaptureLoop(DXGIManager manager)
{
using (Mat mat = new Mat(new OpenCvSharp.Size(manager.Width, manager.Height), MatType.CV_8UC1))
{
while (true)
{
int signalId = EventWaitHandle.WaitAny(_waitSignals, Timeout.Infinite);
if (signalId == 0)
{
break;
}
IntPtr dstPtr = mat.Data;
if (manager.Capture(copyFrameBuffer, dstPtr, 1000) == true)
{
using (Mat last = mat.CvtColor(ColorConversionCodes.GRAY2BGRA))
{
DataPointer dataPointer = new DataPointer(last.Data, (int)last.Total() * last.Channels());
SharpDX.Direct2D1.Bitmap bitmap = _renderTarget.CreateBitmap(dataPointer);
if (bitmap != null)
{
_queue.Add(bitmap);
_count++;
this.Invoke((Action)(() => this.Invalidate()));
}
}
}
}
}
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
부가적으로 하나 더 설명을 드리면, OpenCvSharp은 C/C++ 컴파일된 OpenCvSharpExtern.dll에서 export된 함수들을 래퍼한 Managed DLL인 OpenCvSharp.*.dll에 의해 접근하는 형식입니다.
따라서, OpenCvSharp.*.dll 들에서 제공하지 않으면 OpenCV의 원래 기능들을 사용할 수 없게 되는데요. 이럴 때 별도로 C/C++ DLL을 만들어 원하는 기능을 export 시켜야만 합니다. 하지만, 그보다 더 쉬운 방법이 있는데 바로 DllImport를 이용해 직접 OpenCvSharpExtern.dll에서 export한 함수들을 사용하는 것입니다.
예를 들어 볼까요? OpenCV의 Math 타입은 다음과 같이 포인터 변수를 받는 생성자도 제공합니다.
// https://docs.opencv.org/trunk/d3/d63/classcv_1_1Mat.html#a51615ebf17a64c968df0bf49b4de6a3a
cv::Mat::Mat(int rows, int cols, int type, void * data, size_t step = AUTO_STEP)
그런데 OpenCvSharp의 Mat에는 void* 타입을 데이터로 받는 생성자를 제공하지 않습니다. 이런 경우 아래의 글에서 설명한 데로,
C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling
; https://www.sysnet.pe.kr/2/0/11132
원하는 함수의 signature를 (depends.exe 등의 도구로) 알아낸 다음,
??0Mat@cv@@QEAA@HHHPEAX_K@Z
==> (undecorated name) cv::Mat::Mat(int,int,int,void *,unsigned __int64)
다음과 같은 extern 메서드로 연결할 수 있습니다.
[DllImport("OpenCvSharpExtern.dll", EntryPoint = "??0Mat@cv@@QEAA@HHHPEAX_K@Z")]
internal static unsafe extern void CreateMatWithPtr(IntPtr thisPtr, int rows, int cols, int type, void* data, int step = 0);
// x64 호출 규약
// 클래스의 생성자이기 때문에 첫 번째 매개변수는 반드시 this 포인터
이를 이용해 이번 글에서 작성한 copyFrameBuffer의 Mat 사용을 다음과 같이 변경할 수 있습니다.
unsafe void copyFrameBuffer(IntPtr srcPtr, IntPtr dstPtr, int srcPitch, Rectangle offsetBounds)
{
// 아래의 코드 대신,
// Mat mat = new Mat(new int[] { offsetBounds.Height, offsetBounds.Width }, MatType.CV_8UC4, srcPtr);
// 이렇게 void* 타입을 받는 생성자를 직접 호출
Mat mat = new Mat();
byte* pBuf = (byte *)srcPtr.ToPointer();
CreateMatWithPtr(mat.CvPtr, offsetBounds.Height, offsetBounds.Width, MatType.CV_8UC4, pBuf, 0);
// ...[생략]...
}
이 정도면, .NET Managed 래퍼로 인한 제약을 어느 정도는 C/C++ DLL을 별도로 제작하는 수고를 들이지 않고 극복할 수 있을 것입니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]