성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법</h1> <p> 예전 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11400'>https://www.sysnet.pe.kr/2/0/11400</a> C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12238'>https://www.sysnet.pe.kr/2/0/12238</a> </pre> <br /> 화면 캡처하는 방법을 소개했는데요, 이에 대해 다음의 질문이 들어왔군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# SharpDX 화면 캡쳐 관련해서 질문 드립니다. ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5587'>https://www.sysnet.pe.kr/3/0/5587</a> </pre> <br /> 그러니까, 해당 질문을 했던 소스 코드를 구현해 보면 다음과 같이 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 // <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/desktop/winforms/advanced/how-to-set-jpeg-compression-level'>https://learn.microsoft.com/en-us/dotnet/desktop/winforms/advanced/how-to-set-jpeg-compression-level</a> <span style='color: blue; font-weight: bold'>{ 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()); }</span> this.BeginInvoke((Action)(() => this.Invalidate())); } } </pre> <br /> 그리고 질문하신 분의 의도는, JPG 이미지로 압축을 해 소켓 통신 시 데이터의 양을 줄이고 싶은 것입니다. 실제로, 1920 * 1080 이미지 데이터를 raw 데이터로 전송하면, 1920 * 1080 * 4(rgba)가 되므로 무려 8MB 가까운 크기가 됩니다. 따라서 이미지 압축을 할 만한 충분한 가치가 있는데요. 하나씩 개선을 해볼까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, Capture에 전달한 버퍼를 위에서는 Bitmap에 새롭게 복사하고 있는데요, 사실 화면 캡처 양식과 Bitmap 데이터의 양식이 같으므로 그런 복사 과정을 생략하고 그냥 Bitmap이 마련한 버퍼를 전달하는 것이 가능합니다. 유의해야 하는 것이, <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20240905-00/?p=110224'>Bitmap의 stride 값이 4의 배수</a>(만약 <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20240906-00/?p=110228'>negative stride</a>로 지정했다면!)여야 한다는 것인데, 화면 캡처 데이터가 언제나 4의 배수이므로 사실 굳이 데이터 복사 과정이 필요 없는 것입니다.<br /> <br /> 따라서, 다음과 같이 바꿀 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > _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(<span style='color: blue; font-weight: bold'>bitmapData.Scan0</span>, 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); } </pre> <br /> 코드가 훨씬 간단해졌습니다. 하지만, 실제로 해보면 저렇게 바꿨다고 해서 성능이 그다지 향상되지는 않습니다. 왜냐하면 1920*1080 모니터라고 했을 때 1080번 루프를 도는 것은 컴퓨터에게는 껌이기 때문입니다. ^^;<br /> <br /> <hr style='width: 50%' /><br /> <br /> 하지만, (그래도 찜찜한 stride도 그렇고) 굳이 Bitmap을 써야 할 필요가 있을까요? 개인적으로 예전에 OpenCV의 성능이 매우 믿음직스러웠는데요, ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 내가 만든 코드보다 OpenCV의 속도가 월등히 빠른 이유 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11423'>https://www.sysnet.pe.kr/2/0/11423</a> </pre> <br /> 따라서 OpenCV에 이 과정을 맡기면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > OpenCV 응용 프로그램을 C#으로 구현 - OpenCvSharp ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11402'>https://www.sysnet.pe.kr/2/0/11402</a> C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11407'>https://www.sysnet.pe.kr/2/0/11407</a> </pre> <br /> 다음과 같이 재구성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://www.sysnet.pe.kr/2/0/12887'>Install-Package OpenCvSharp4</a> <span style='color: blue; font-weight: bold'>Mat src = new Mat(manager.Height, manager.Width, MatType.CV_8UC4);</span> if (manager.Capture(<span style='color: blue; font-weight: bold'>src.Data</span>, 1000) == true) { ImageEncodingParam[] imParams = new ImageEncodingParam[1]; imParams[0] = new ImageEncodingParam(ImwriteFlags.JpegQuality, 80); byte[] jpgBuffer = <span style='color: blue; font-weight: bold'>src.ImEncode(".jpg", imParams);</span> // File.WriteAllBytes("c:\\temp\\test_mat.jpg", jpgBuffer); // 이미지가 정상인지 테스트 } </pre> <br /> 코드가 상당히 간결해져서 마음에 듭니다. ^^ 그리고 저렇게 해서 나온 최종 크기는 (제 경우에) 1920*1080 화면 캡처 시 178KB 정도입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 하지만, 정말 성능이 개선되었을지 테스트를 해봐야 합니다. ^^<br /> <br /> <a target='tab' href='https://www.sysnet.pe.kr/0/0/535'>제 컴퓨터 기준</a>으로, 처음에 Bitmap을 경유해 JPG로 변환하는 것은 11~12ms 정도가 나왔습니다. 반면 OpenCVSharp으로 바꾼 경우에는 6~7ms가 나옵니다. 시간 상으로는 근소하지만 비율로는 2배 가까이 빨라졌으므로 고속 처리할 때는 OpenCVSharp을 사용할 의미가 충분히 있습니다. ^^<br /> <br /> 한 가지 재미있는 점은, OpenCVSharp의 옛날 버전으로 하는 경우 성능이 더 안 좋게 나옵니다. 가령 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/11407'>C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV</a>" 글에서 테스트했던 "3.3.1.20171117" 버전에서 위의 테스트를 해보면 24~26ms가 나옵니다.<br /> <br /> 그러니까, 구 버전의 OpenCVSharp에서 제공하는 Mat.ImEncode의 성능이 닷넷에서 원래 제공하던 ImageCodecInfo보다 2배 넘게 느린 것입니다.<br /> <br /> 따라서 가능한 최신 버전으로 업데이트하시고... 기왕이면 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12887'>새로 나온 OpenCvSharp4를 설치</a>하는 것이 좋겠습니다. ^^<br /> <br /> 참고로, JPEG Encoder를 직접 C#으로 만드는 것도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > A Simple JPEG Encoder in C# ; <a target='tab' href='https://www.codeproject.com/Articles/83225/A-Simple-JPEG-Encoder-in-C'>https://www.codeproject.com/Articles/83225/A-Simple-JPEG-Encoder-in-C</a> </pre> <br /> 공부 차원에서는 저 소스 코드를 보면 되겠지만, 실제 업무에서는 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/11423'>내가 만든 코드보다 OpenCV의 속도가 월등히 빠른 이유</a>"의 글에서처럼 C/C++의 힘을 빌리는 것이 성능상 더 좋습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1874&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1886
(왼쪽의 숫자를 입력해야 합니다.)