성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - OpenCvSharp 사용 시 C/C++을 이용한 속도 향상 (for 루프 연산)</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# - OpenCvSharp을 이용한 동영상(avi, mp4, ...) 처리 + Direct2D ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11404'>http://www.sysnet.pe.kr/2/0/11404</a> </pre> <br /> RGB 이미지를 RGBA로 변환하기 위해 C# 코딩을 했었는데요, 이게 꽤나 성능이 안 좋았습니다. 반면, OpenCvSharp의 Mat 타입에서 제공하는 CvtColor 연산은 놀라울 정도로 높은 성능을 보였습니다. 따라서 당연히 CvtColor 메서드를 비롯해 가능하면 OpenCV가 제공하는 함수를 사용하는 것이 좋겠지만, 그래도 때로는 사용자 정의 루프를 작성해야 할 때가 있습니다.<br /> <br /> 그럴 때 성능이 안 좋은 C# 코딩보다는 그 부분만을 C/C++로 대체해 OpenCV 수준의 성능으로 끌어올리는 것이 바람직한데요, 그런 경우 어떤 식으로 해야 OpenCV 정도까지 성능이 개선되는지 테스트를 해봤습니다. ^^<br /> <br /> 연산 대상은 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11404'>C# - OpenCvSharp을 이용한 동영상(avi, mp4, ...) 처리 + Direct2D</a>" 글에서도 다뤘던 RGB to RGBA 코드입니다.<br /> <br /> <hr style='width: 50%' /><br /> <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;' > static void Main(string[] args) { using (Mat mat = new Mat(new Size(<span style='color: blue; font-weight: bold'>1920, 1080</span>), MatType.CV_8UC3)) { Convert(mat); } } static int Convert(Mat mat) { using (Mat dstMat = mat.<span style='color: blue; font-weight: bold'>CvtColor(ColorConversionCodes.BGR2BGRA)</span>) { return dstMat.Width; } } </pre> <br /> 그다음 동일한 연산을 C#으로 직접 For loop로 구현을 했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static unsafe int Convert2(Mat srcMat) { byte *srcPtr = (byte *)srcMat.Data; using (Mat dstMat = new Mat(new Size(1920, 1080), MatType.CV_8UC4)) { byte *dstPtr = (byte *)dstMat.Data; for (int y = 0; y < srcMat.Height; y++) { for (int x = 0; x < srcMat.Width - 1; x++) { int* src = (int*)srcPtr; int* dst = (int*)dstPtr; *dst = *src; *(dstPtr + 3) = 0xff; srcPtr = srcPtr + 3; dstPtr = dstPtr + 4; } Buffer.MemoryCopy(srcPtr, dstPtr, 3, 3); *(dstPtr + 3) = 0xff; } } return 0; } </pre> <br /> 역시 동일한 코드를 C/C++ DLL을 만들어 export 함수로 C#에서 다음과 같이 호출하는 식으로 구현했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ==== C# ==== [DllImport("MemCopyLib.dll")] public unsafe extern static void RGB2RGBA(byte* srcPtr, byte* dstPtr, int width, int height); static unsafe int Convert3(Mat srcMat) { byte* srcPtr = (byte*)srcMat.Data; using (Mat dstMat = new Mat(new Size(1920, 1080), MatType.CV_8UC4)) { byte* dstPtr = (byte*)dstMat.Data; RGB2RGBA(srcPtr, dstPtr, srcMat.Width, srcMat.Height); } return 0; } </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ==== C/C++ ==== __declspec(dllexport) void RGB2RGBA(BYTE *srcPtr, BYTE *dstPtr, int width, int height) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { memcpy(dstPtr, srcPtr, 3); *(dstPtr + 3) = 0xff; srcPtr = srcPtr + 3; dstPtr = dstPtr + 4; } } } </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;' > // 각각 Release 빌드로 100회씩 실행했으며, JIT 컴파일 보정을 위해 1회를 미리 실행한 후 시간 측정 opencv(100) : 690 c# for(100) : 7120 C++ for(100) : 1284 </pre> <br /> 보면 OpenCV의 Mat.CvtColor 메서드가 단연 빠르고, 약 2배 늦은 속도로 단순 C++ for 구문 속도가 나오며 C#은 그보다도 7배까지 느린 것을 볼 수 있습니다. 즉, 이미지 처리 시 무거운 for 루프를 처리하는 경우라면 C#보다는 C/C++에 작업을 맡기는 것을 충분히 고려할만합니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='parallel'></a> <br /> 그렇다면 OpenCV의 처리가 왜 그토록 빠른 것일까요? 이에 대한 해답은 <a target='tab' href='http://www.sysnet.pe.kr/2/0/11404'>지난번</a>에 소개했던 것처럼 다음의 글에서 찾아볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > OpenCV - 속도 분석 (1) ; <a target='tab' href='https://laonple.blog.me/220861902363'>https://laonple.blog.me/220861902363</a> </pre> <br /> 즉, 병렬 처리입니다. 이를 위해 C/C++ 코드를 다음과 같이 병렬 처리로 바꾸면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // i5-4670 코어 4개에서 테스트 #include <ppl.h> using namespace concurrency; __declspec(dllexport) void RGB2RGBA_Parallel(BYTE *srcPtr, BYTE *dstPtr, int width, int height) { <span style='color: blue; font-weight: bold'>parallel_for (0, height, [&](size_t y)</span> { BYTE *srcPtrY = srcPtr + (y * width * 3); BYTE *dstPtrY = dstPtr + (y * width * 4); for (size_t x = 0; x < width; x++) { memcpy(dstPtrY, srcPtrY, 3); *(dstPtrY + 3) = 0xff; srcPtrY = srcPtrY + 3; dstPtrY = dstPtrY + 4; } }); } </pre> <br /> 1284ms였던 것이 609ms까지 내려갑니다. OpenCV 속도가 690ms이니 충분히 빨라진 것입니다. (물론, OpenCV가 범용 처리를 하는 것을 고려했을 때 당연히 우리가 만든 C/C++ 코드가 저 정도는 빨라야 합니다.)<br /> <br /> 그렇다면 혹시 C# 코드도 병렬 처리를 하면 많이 빨라질까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // i5-4670 코어 4개에서 테스트 static unsafe int Convert3(Mat srcMat) { byte* srcPtr = (byte*)srcMat.Data; using (Mat dstMat = new Mat(new Size(1920, 1080), MatType.CV_8UC4)) { byte* dstPtr = (byte*)dstMat.Data; <span style='color: blue; font-weight: bold'>Parallel.For(0, srcMat.Height, (y) =></span> { byte* srcPtrY = srcPtr + (y * srcMat.Width * 3); byte* dstPtrY = dstPtr + (y * srcMat.Width * 4); for (int x = 0; x < srcMat.Width - 1; x++) { int* src = (int*)srcPtrY; int* dst = (int*)dstPtrY; *dst = *src; *(dstPtrY + 3) = 0xff; srcPtrY = srcPtrY + 3; dstPtrY = dstPtrY + 4; } Buffer.MemoryCopy(srcPtrY, dstPtrY, 3, 3); *(dstPtrY + 3) = 0xff; }); } return 0; } </pre> <br /> 테스트해보면, 단독 스레드일 때 7355ms인 것에 비하면 2036ms를 기록하며 확실히 빨라졌지만 이는 C/C++ 단독 스레드 처리일 때보다도 느린 수치입니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1210&boardid=331301885'>첨부 파일은 이 글의 소스 코드를 포함</a>합니다.)<br /> <br /> 참고로 다음은 성능 수치를 엑셀 그래프로 그린 것입니다. 훨씬 직관적이군요. ^^<br /> <br /> <img alt='for_loop_perf.png' src='/SysWebRes/bbs/for_loop_perf.png' /><br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1798
(왼쪽의 숫자를 입력해야 합니다.)