Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 10개 있습니다.)
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)
; https://www.sysnet.pe.kr/2/0/11385

.NET Framework: 705. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드
; https://www.sysnet.pe.kr/2/0/11400

.NET Framework: 706. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력
; https://www.sysnet.pe.kr/2/0/11401

.NET Framework: 712. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV
; https://www.sysnet.pe.kr/2/0/11407

.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)
; https://www.sysnet.pe.kr/2/0/11408

.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
; https://www.sysnet.pe.kr/2/0/12238

.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작
; https://www.sysnet.pe.kr/2/0/12895

.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리
; https://www.sysnet.pe.kr/2/0/12897

.NET Framework: 1152. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 (저해상도 현상 해결)
; https://www.sysnet.pe.kr/2/0/12963




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을 별도로 제작하는 수고를 들이지 않고 극복할 수 있을 것입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 12/18/2017]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13079정성태6/17/20224643오류 유형: 814. 파이썬 - Error: The file/path provided (...) does not appear to exist
13078정성태6/16/20226691.NET Framework: 2021. WPF - UI Thread와 Render Thread파일 다운로드1
13077정성태6/15/20227017스크립트: 40. 파이썬 - PostgreSQL 환경 구성
13075정성태6/15/20225975Linux: 50. Linux - apt와 apt-get의 차이 [2]
13074정성태6/13/20226294.NET Framework: 2020. C# - NTFS 파일에 사용자 정의 속성값 추가하는 방법파일 다운로드1
13073정성태6/12/20226517Windows: 207. Windows Server 2022에 도입된 WSL 2
13072정성태6/10/20226790Linux: 49. Linux - ls 명령어로 출력되는 디렉터리 색상 변경 방법
13071정성태6/9/20227362스크립트: 39. Python에서 cx_Oracle 환경 구성
13070정성태6/8/20227179오류 유형: 813. Windows 11에서 입력 포커스가 바뀌는 문제 [1]
13069정성태5/26/20229401.NET Framework: 2019. C# - .NET에서 제공하는 3가지 Timer 비교 [2]
13068정성태5/24/20227893.NET Framework: 2018. C# - 일정 크기를 할당하는 동안 GC를 (가능한) 멈추는 방법 [1]파일 다운로드1
13067정성태5/23/20227223Windows: 206. Outlook - 1년 이상 지난 메일이 기본적으로 안 보이는 문제
13066정성태5/23/20226550Windows: 205. Windows 11 - Windows + S(또는 Q)로 뜨는 작업 표시줄의 검색 바가 동작하지 않는 경우
13065정성태5/20/20227227.NET Framework: 2017. C# - Windows I/O Ring 소개 [2]파일 다운로드1
13064정성태5/18/20226752.NET Framework: 2016. C# - JIT 컴파일러의 인라인 메서드 처리 유무
13063정성태5/18/20227171.NET Framework: 2015. C# - 인라인 메서드(inline methods)
13062정성태5/17/20227989.NET Framework: 2014. C# - async/await 그리고 스레드 (4) 비동기 I/O 재현파일 다운로드1
13061정성태5/16/20226799.NET Framework: 2013. C# - FILE_FLAG_OVERLAPPED가 적용된 파일의 읽기/쓰기 시 Position 관리파일 다운로드1
13060정성태5/15/20229279.NET Framework: 2012. C# - async/await 그리고 스레드 (3) Task.Delay 재현파일 다운로드1
13059정성태5/14/20227732.NET Framework: 2011. C# - CLR ThreadPool의 I/O 스레드에 작업을 맡기는 방법 [1]파일 다운로드1
13058정성태5/13/20227642.NET Framework: 2010. C# - ThreadPool.SetMaxThreads 사용법
13057정성태5/12/20229314오류 유형: 812. 파이썬 - ImportError: cannot import name ...
13056정성태5/12/20226450.NET Framework: 2009. C# - async/await 그리고 스레드 (2) MyTask의 호출 흐름 [2]파일 다운로드1
13055정성태5/11/20229380.NET Framework: 2008. C# - async/await 그리고 스레드 (1) MyTask로 재현 [11]파일 다운로드1
13054정성태5/11/20226869.NET Framework: 2007. C# - 10진수 숫자를 담은 문자열을 숫자로 변환하는 방법 [11]파일 다운로드1
13053정성태5/10/20226519.NET Framework: 2006. C# - GC.KeepAlive 메서드의 역할
... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...