Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 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) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법

예전 글에서,

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

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

화면 캡처하는 방법을 소개했는데요, 이에 대해 다음의 질문이 들어왔군요. ^^

C# SharpDX 화면 캡쳐 관련해서 질문 드립니다.
; https://www.sysnet.pe.kr/3/0/5587

그러니까, 해당 질문을 했던 소스 코드를 구현해 보면 다음과 같이 할 수 있습니다.

private void CaptureLoop(DXGIManager manager, byte[] frame)
{
    while (true)
    {
        int signalId = EventWaitHandle.WaitAny(_waitSignals, Timeout.Infinite);

        if (signalId == 0)
        {
            break;
        }

        _bitmap = new Bitmap(manager.Width, manager.Height, PixelFormat.Format32bppArgb);

        Rectangle boundsRect = new Rectangle(0, 0, manager.Width, manager.Height);
        BitmapData bitmapData = _bitmap.LockBits(boundsRect, ImageLockMode.ReadWrite, _bitmap.PixelFormat);

        if (manager.Capture(frame, 1000) == true)
        {
            GCHandle pinnedArray = GCHandle.Alloc(frame, GCHandleType.Pinned);
            IntPtr srcPtr = pinnedArray.AddrOfPinnedObject();
            IntPtr dstPtr = bitmapData.Scan0;

            for (int y = 0; y < manager.Height; y++)
            {
                Utilities.CopyMemory(dstPtr, srcPtr, manager.Width * 4);
                srcPtr = IntPtr.Add(srcPtr, manager.Width * 4);
                dstPtr = IntPtr.Add(dstPtr, bitmapData.Stride);
            }

            pinnedArray.Free();
        }

        _bitmap.UnlockBits(bitmapData);

        // How to: Set JPEG Compression Level
        // https://learn.microsoft.com/en-us/dotnet/desktop/winforms/advanced/how-to-set-jpeg-compression-level
        {
            ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);

            System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
            EncoderParameters myEncoderParameters = new EncoderParameters(1);
            EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 80L);
            myEncoderParameters.Param[0] = myEncoderParameter;

            MemoryStream jpgStream = new MemoryStream();
            _bitmap.Save(jpgStream, jpgEncoder, myEncoderParameters);

            // File.WriteAllBytes("c:\\temp\\test.jpg", jpgStream.GetBuffer());
        }

        this.BeginInvoke((Action)(() => this.Invalidate()));
    }
}

그리고 질문하신 분의 의도는, JPG 이미지로 압축을 해 소켓 통신 시 데이터의 양을 줄이고 싶은 것입니다. 실제로, 1920 * 1080 이미지 데이터를 raw 데이터로 전송하면, 1920 * 1080 * 4(rgba)가 되므로 무려 8MB 가까운 크기가 됩니다. 따라서 이미지 압축을 할 만한 충분한 가치가 있는데요. 하나씩 개선을 해볼까요? ^^




우선, Capture에 전달한 버퍼를 위에서는 Bitmap에 새롭게 복사하고 있는데요, 사실 화면 캡처 양식과 Bitmap 데이터의 양식이 같으므로 그런 복사 과정을 생략하고 그냥 Bitmap이 마련한 버퍼를 전달하는 것이 가능합니다. 유의해야 하는 것이, Bitmap의 stride 값이 4의 배수(만약 negative stride로 지정했다면!)여야 한다는 것인데, 화면 캡처 데이터가 언제나 4의 배수이므로 사실 굳이 데이터 복사 과정이 필요 없는 것입니다.

따라서, 다음과 같이 바꿀 수 있습니다.

_bitmap = new Bitmap(manager.Width, manager.Height, PixelFormat.Format32bppArgb);

Rectangle boundsRect = new Rectangle(0, 0, manager.Width, manager.Height);
BitmapData bitmapData = _bitmap.LockBits(boundsRect, ImageLockMode.ReadWrite, _bitmap.PixelFormat);

if (manager.Capture(bitmapData.Scan0, 1000) == true)
{
    ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);

    System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
    EncoderParameters myEncoderParameters = new EncoderParameters(1);
    EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 80L);
    myEncoderParameters.Param[0] = myEncoderParameter;

    MemoryStream jpgStream = new MemoryStream();
    _bitmap.Save(jpgStream, jpgEncoder, myEncoderParameters);
    // File.WriteAllBytes("c:\\temp\\test_bmp.jpg", jpgStream.GetBuffer());

    _bitmap.UnlockBits(bitmapData);
}

코드가 훨씬 간단해졌습니다. 하지만, 실제로 해보면 저렇게 바꿨다고 해서 성능이 그다지 향상되지는 않습니다. 왜냐하면 1920*1080 모니터라고 했을 때 1080번 루프를 도는 것은 컴퓨터에게는 껌이기 때문입니다. ^^;




하지만, (그래도 찜찜한 stride도 그렇고) 굳이 Bitmap을 써야 할 필요가 있을까요? 개인적으로 예전에 OpenCV의 성능이 매우 믿음직스러웠는데요, ^^

내가 만든 코드보다 OpenCV의 속도가 월등히 빠른 이유
; https://www.sysnet.pe.kr/2/0/11423

따라서 OpenCV에 이 과정을 맡기면,

OpenCV 응용 프로그램을 C#으로 구현 - OpenCvSharp
; https://www.sysnet.pe.kr/2/0/11402

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

다음과 같이 재구성할 수 있습니다.

// Install-Package OpenCvSharp4 

Mat src = new Mat(manager.Height, manager.Width, MatType.CV_8UC4);
if (manager.Capture(src.Data, 1000) == true)
{
    ImageEncodingParam[] imParams = new ImageEncodingParam[1];
    imParams[0] = new ImageEncodingParam(ImwriteFlags.JpegQuality, 80);

    byte[] jpgBuffer = src.ImEncode(".jpg", imParams);

    // File.WriteAllBytes("c:\\temp\\test_mat.jpg", jpgBuffer); // 이미지가 정상인지 테스트
}

코드가 상당히 간결해져서 마음에 듭니다. ^^ 그리고 저렇게 해서 나온 최종 크기는 (제 경우에) 1920*1080 화면 캡처 시 178KB 정도입니다.




하지만, 정말 성능이 개선되었을지 테스트를 해봐야 합니다. ^^

제 컴퓨터 기준으로, 처음에 Bitmap을 경유해 JPG로 변환하는 것은 11~12ms 정도가 나왔습니다. 반면 OpenCVSharp으로 바꾼 경우에는 6~7ms가 나옵니다. 시간 상으로는 근소하지만 비율로는 2배 가까이 빨라졌으므로 고속 처리할 때는 OpenCVSharp을 사용할 의미가 충분히 있습니다. ^^

한 가지 재미있는 점은, OpenCVSharp의 옛날 버전으로 하는 경우 성능이 더 안 좋게 나옵니다. 가령 "C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV" 글에서 테스트했던 "3.3.1.20171117" 버전에서 위의 테스트를 해보면 24~26ms가 나옵니다.

그러니까, 구 버전의 OpenCVSharp에서 제공하는 Mat.ImEncode의 성능이 닷넷에서 원래 제공하던 ImageCodecInfo보다 2배 넘게 느린 것입니다.

따라서 가능한 최신 버전으로 업데이트하시고... 기왕이면 새로 나온 OpenCvSharp4를 설치하는 것이 좋겠습니다. ^^

참고로, JPEG Encoder를 직접 C#으로 만드는 것도 가능합니다.

A Simple JPEG Encoder in C#
; https://www.codeproject.com/Articles/83225/A-Simple-JPEG-Encoder-in-C

공부 차원에서는 저 소스 코드를 보면 되겠지만, 실제 업무에서는 "내가 만든 코드보다 OpenCV의 속도가 월등히 빠른 이유"의 글에서처럼 C/C++의 힘을 빌리는 것이 성능상 더 좋습니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/7/2024]

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

비밀번호

댓글 작성자
 




... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1100정성태8/17/201128891.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점파일 다운로드1
1099정성태8/15/201128332오류 유형: 132. 어느 순간 갑자기 접속이 안 되는 TFS 서버
1098정성태8/15/201150356웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [2]
1097정성태8/15/201121655.NET Framework: 235. 메서드의 메타 데이터 토큰 값으로 클래스를 찾아내는 방법
1096정성태8/15/201125793디버깅 기술: 42. Watson Bucket 정보를 이용한 CLR 응용 프로그램 예외 분석 - (2)
1095정성태8/14/201126212디버깅 기술: 41. Windbg - 비정상 종료된 닷넷 프로그램의 StackTrace에서 보이는 offset 값 의미
1094정성태8/14/201130612오류 유형: 131. Fiddler가 강제 종료된 경우, 웹 사이트 방문이 안되는 현상
1093정성태7/27/201124225오류 유형: 130. Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor ... Access is denied.
1092정성태7/22/201126636Team Foundation Server: 46. 코드 이외의 파일에 대해 소스 제어에서 제외시키는 방법
1091정성태7/21/201125676개발 환경 구성: 128. WP7 Emulator 실행 시 audiodg.exe의 CPU 소모율 증가 [2]
1089정성태7/18/201131253.NET Framework: 234. 왜? Button 컨트롤에는 MouseDown/MouseUp 이벤트가 발생하지 않을까요?파일 다운로드1
1088정성태7/16/201124291.NET Framework: 233. Entity Framework 4.1 - 윈도우 폰 7에서의 CodeFirst 순환 참조 문제파일 다운로드1
1087정성태7/15/201127021.NET Framework: 232. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 - 두 번째 이야기파일 다운로드1
1086정성태7/14/201128437.NET Framework: 231. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 [1]파일 다운로드1
1085정성태7/14/201128891.NET Framework: 230. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류 - 두 번째 이야기파일 다운로드1
1084정성태7/11/201134175.NET Framework: 229. SQL 서버 - DB 테이블의 데이터 변경에 대한 알림 처리 [4]파일 다운로드1
1083정성태7/11/201128220.NET Framework: 228. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류
1082정성태7/10/201127782.NET Framework: 227. basicHttpBinding + 사용자 정의 인증 구현 [2]파일 다운로드1
1081정성태7/9/201127105VC++: 53. Windows 7에서 gcc.exe 실행 시 Access denied 오류 [2]
1080정성태7/8/201125607웹: 23. Sysnet 웹 사이트의 HTML5 변환 기록 - 두 번째 이야기파일 다운로드1
1079정성태7/6/201130024오류 유형: 129. Hyper-V + Realtek 랜카드가 설치된 시스템의 BSOD 현상 [2]
1078정성태7/5/201137527VC++: 52. Chromium 컴파일하는 방법 [2]
1077정성태6/24/201135160.NET Framework: 226. HttpWebRequest 타입의 HaveResponse 속성 이야기파일 다운로드1
1076정성태6/23/201129319오류 유형: 128. SQL Express - User Instance 옵션을 사용한 경우 발생하는 오류 메시지 유형 2가지
1075정성태6/21/201124921VS.NET IDE: 69. 윈폰 프로젝트에서 WCF 서비스 참조할 때 Reference.cs 파일이 비어있는 경우
1074정성태6/20/201125023.NET Framework: 225. 닷넷 네트워크 라이브러리의 트레이스 기능파일 다운로드1
... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...