자유롭게 질의 및 응답을 할 수 있는 게시판입니다. 개발자 여러분의 답변이 큰 도움이 됩니다.
- 제품설치/등록 오류 문의: 설치/등록 Q&A 이용 (제품 구매 고객 한정)
Delphi FMX 멀티스레드 문제입니다.
2019.08.28 16:05
본 게시판은 개발자들이 자유롭게 질문과 답변을 공유하는 게시판입니다.
* 따라서 최대한 정중하게 질문을 올려 주세요.
* 질문을 상세히 작성해 주실 수록 좋은 답변이 올라 옵니다.
* 다른 분들도 참고할 수 있도록 결과 댓글 필수(또는 감사 댓글)
(결과 댓글을 달지 않는 경우 다음 질문에 대한 답변이 달리지 않는 불이익이 있을 수 있습니다.)
-----------------------------------------------------------------------------------------------
안녕하세요.
날씨가 많이 시원해졌네요.
OpenCV 영상 프로젝트는 여전히 C#과 Delphi 사이에서 고민하고 있는 1인입니다.
C#으로 프로젝트 진행하면서 틈틈히 Delphi에서 핵심적인 부분만 테스트해 보고있는데 멀티스레드에서 막히네요.
OpenCV로 영상을 여러개 불러올때 위의 9개 정도 불러올때까진 괜찮습니다.
약, 16개 정도 불러오면 그림에서 빨간색 테두리(시간표시)를 보면 1초단위가 아니라 널뛰기를 합니다.
즉, 화면이 부드럽지 못하고 끊어진다는 뜻이겠죠.
시스템 스펙은
CPU : Intel Xwon Silver 4114 2.20 x 2 (총 20코어)
메모리 : 32Gb
GPU : NIVDIA Quadro P620 전용메모리 2Gb x 2
작업관리자에서 보면 CPU는 5% 정도로써 놀고있습니다.
메모리, GPU 모두 넉넉한 상태구요.
내부 네트웍은 기가랜입니다.
그런데 자원을 제대로 사용하지 못하는건지 영상이 정상적으로 재생되지 못하고 왜 널뛰기를 하는 것인지 잘 모르겠습니다.
주요 코드는 아래와 같습니다.
[스레드 선언]
type
TCHThread = class(TThread)
private
i_TH_Ch : smallint;
TH_Rect_1, TH_Rect_2, TH_Rect_3, TH_Rect_4 : tcvRect;
procedure Proc_Disp(i_Ch : smallint);
protected
procedure Execute ; override;
public
constructor Create(i_Thread_Ch: smallint; Rect_1, Rect_2, Rect_3, Rect_4 : tcvRect);
destructor Destroy; override;
end;
[스레드생성]
constructor TCHThread.Create(i_Thread_Ch: smallint; Rect_1, Rect_2, Rect_3, Rect_4 : tcvRect);
begin
inherited Create(True);
i_TH_Ch := i_Thread_Ch;
TH_Rect_1 := Rect_1; TH_Rect_2 := Rect_2; TH_Rect_3 := Rect_3; TH_Rect_4 := Rect_4;
{
TThreadPriority WIN32 스레드 우선 순위 상수 값
tpIdle THREAD_PRIORITY_IDLE -15
tpLowest THREAD_PRIORITY_LOWEST -2
tpLower THREAD_PRIORITY_BELOW_NORMAL -1
tpNomal THREAD_PRIORITY_NOMAL 0
tpHigher THREAD_PRIORITY_ABOVE_NOMAL 1
tpHighest THREAD_PRIORITY_HIGHEST 2
tpTimeCritical THREAD_PRIORITY_TIME_CRITICAL 15
}
Priority := tpLowest; // CPU의 스레드 우선순위 정의(현재로썬 뭘 해도 차이없음)
Self.FreeOnTerminate := False;
Resume;
end;
[스레드실행]
procedure TCHThread.Execute;
var
i : smallint;
Src_Img : pIplImage;
Dest_Img : pIplImage;
P1_Img, P2_Img, P3_Img, P4_Img : pIplImage;
// 회전에 필요
rot_mat: pCvMat;
scale: double;
center: TcvPoint2D32f;
begin
inherited;
i := i_TH_Ch;
while Assigned(FormMain.capture[i]) do
begin
LockValue.Acquire;
try
FormMain.frame[i] := cvQueryFrame(FormMain.capture[i]);
if Assigned(FormMain.frame[i]) then
begin
// 편의상 코드 삭제(영상을 자르고, 회전하고 병합하는 코드)
try
TImage(FormMain.FindComponent('Disp'+IntToStr(i))).Bitmap.Canvas.BeginScene;
finally
IPLImageToFMXBitmap(Dest_Img, TImage(FormMain.FindComponent('Disp'+IntToStr(i))).Bitmap, False);
TImage(FormMain.FindComponent('Disp'+IntToStr(i))).Bitmap.Canvas.EndScene;
end;
cvReleaseImage(Dest_Img);
cvReleaseImage(Src_Img);
end;
finally
LockValue.Release;
end;
end;
end;
[스레드 생성하고 실행부분]
procedure TFormMain.Proc_Set_Display(i_Ch, i_Cam_Idx : Integer);
var
i : Integer;
s_Url : AnsiString;
begin
if i_Cam_Idx > 0 then
begin
중략
capture[i_Ch] := cvCreateFileCapture(PAnsiChar(s_Url));
if Assigned(capture[i_Ch]) then
begin
b_Assign := True;
cvSetCaptureProperty(capture[i_Ch], CV_CAP_PROP_FPS, 15.0);
cvSetCaptureProperty(capture[i_Ch], CV_CAP_PROP_FOURCC, CV_FOURCC('H','2','6','4'));
// ★★★★ 스레드 생성하고 실행(스레드를 배열로 두었습니다) ★★★★★
LockValue := TCriticalSection.Create;
CH_Thread[i_Ch] := TCHThread.Create(i_Ch, Rect_1[i_Ch], Rect_2[i_Ch], Rect_3[i_Ch], Rect_4[i_Ch]);
end;
end;
end;
end;
10개의 영상을 로딩할 경우 CH_Thread[1] ~ CH_Threadp10]으로 10개가 생성되고 서로 다른 영상을 스레드 돌리게됩니다.
실제 procedure TCHThread.Execute; 를 보게되면
while Assigned(FormMain.capture[i]) do 문이 있습니다.
영상이 유효하면 계속 영상의 프레임을 받아서 자르고, 회전하여 TImage에 뿌려주라는 것입니다.
스레드 한번 수행하는데 위처럼 해당 스레드가 계속 반복하도록 만든게 문제일까요?
그렇다면 매번 한프레임 한프레임을 스레드 생성하여 영상을 자르고, 회전하여 TImage에 뿌려주라고 하는게 맞는걸까요?
시스템 자원이 부족한 문제가 아닌데 이렇게 영상이 뚝뚝 끊어지는 이유를 더더욱 모르겠습니다;;
경험 있으신 분께 도움 요청드립니다.
델파이에서의 멀티스레드 구현 방법도 여러가지인거 같은데 어떻게 구현하는 것이 합리적인지 모르겠습니다;;
급해서 관련 문제를 해결하기위해 서핑을 했는데
에서 소개된 AIO - Delphi 용 Coroutine 기반 멀티 스레딩 라이브러리 가 도움이 될까요?
댓글 4
-
김원경
2019.09.02 14:50
-
아크나톤
2019.09.03 01:28
아 Synchronize 메소드로 해결했습니다.. 감사합니다.
큰 차이가 있겠냐 싶었는데 굉장한 차이군요.
감사합니다.
-
happy
2019.09.03 12:04
Synchronize 는 vcl 전체 싱글락이라
Synchronize()가 실행되는 동안 이 함수를 호출하는 나머지 쓰레드들이
작업을 하지 못하고 대기상태에 있어야 합니다.
cpu를 효율적으로 사용하지 못하게 됩니다.
그리고
각기 소스채널에 해당하는 쓰레드가 정해진 채널로 부터
OpenCV에 의해서 버퍼링 되어있는 프레임을 읽는 식으로 코딩되어 있는데
Lock 이 왜 필요 한가요.
무조건 쓰레드 많이 생성한다고 해서 퍼포먼스가 증가하는 건 아닙니다.
쉽게 코딩하기 위해 cvQueryFrame() API를 이용해서 프레임 데이타를 갖고오는 식으로 코딩한거 같은데
쓰레드 풀 몇개 할당해서 소스채널로 부터 각기 채널의 프레임 데이타가 유효한지 체크해서
프레임 버퍼가 유효한 채널만 프레임 데이타를 받아와서 처리하도록 구조를 잡는 게 더 나은 방법 임.
-
아크나톤
2019.09.03 14:45
조언 감사합니다.
영상들은 640x480이며 RTSP로 불러옵니다.
하나의 폼에 최대 16개이고 이것을 10개의 폼에 생성하여 총 160개를 출력하는 것이 목표입니다.
질문에 있는 바와 같이 스펙은 2cpu(총 20코어) + 랩32기가입니다.
처음에 제가 Application.OnIdle안에서 for 루트 돌려서 8의 영상을 받을때까지는 매우 괜찮았습니다.
그런데 12개정도 불러오니 프레임 끊김현상이 발생하지않고 모든 12개 영상의 프레임이 느려지는 현상이 발생하더라구요.
당연히 이 방식은 아닌줄 알고 시도한 것이지만요.
그래서 TThread를 사용해 보았는데 워낙 기본이 안되어있는지라....
원경님께서 말씀하신 방법으로 Synchronize를 사용하니 프레임에는 문제는 없는것 같습니다.
그런데 16개의 영상을 돌리는데 cpu8%로 괜찮은거 같은데 램을 1.4기가정도 먹어버리네요;;;
이 문제도 해결해야할 사항인 것같습니다.
질문을 올릴때 당시 LockValue.Acquire;와 Release등의 코드가 들어있었는데 Sychronize메소드를 사용하면서 모두 제거하였습니다.
말씀하신 쓰레드 풀 할당해서 처리하는 것이 어떤 의미인지 잘 모르겠습니다만 관련하여 구글링해서 한번 해보겠습니다.
조언 감사드립니다.
Delphi FMX 멀티스레드 문제입니다.
2019.08.28 16:05
본 게시판은 개발자들이 자유롭게 질문과 답변을 공유하는 게시판입니다.
* 따라서 최대한 정중하게 질문을 올려 주세요.
* 질문을 상세히 작성해 주실 수록 좋은 답변이 올라 옵니다.
* 다른 분들도 참고할 수 있도록 결과 댓글 필수(또는 감사 댓글)
(결과 댓글을 달지 않는 경우 다음 질문에 대한 답변이 달리지 않는 불이익이 있을 수 있습니다.)
-----------------------------------------------------------------------------------------------
안녕하세요.
날씨가 많이 시원해졌네요.
OpenCV 영상 프로젝트는 여전히 C#과 Delphi 사이에서 고민하고 있는 1인입니다.
C#으로 프로젝트 진행하면서 틈틈히 Delphi에서 핵심적인 부분만 테스트해 보고있는데 멀티스레드에서 막히네요.
OpenCV로 영상을 여러개 불러올때 위의 9개 정도 불러올때까진 괜찮습니다.
약, 16개 정도 불러오면 그림에서 빨간색 테두리(시간표시)를 보면 1초단위가 아니라 널뛰기를 합니다.
즉, 화면이 부드럽지 못하고 끊어진다는 뜻이겠죠.
시스템 스펙은
CPU : Intel Xwon Silver 4114 2.20 x 2 (총 20코어)
메모리 : 32Gb
GPU : NIVDIA Quadro P620 전용메모리 2Gb x 2
작업관리자에서 보면 CPU는 5% 정도로써 놀고있습니다.
메모리, GPU 모두 넉넉한 상태구요.
내부 네트웍은 기가랜입니다.
그런데 자원을 제대로 사용하지 못하는건지 영상이 정상적으로 재생되지 못하고 왜 널뛰기를 하는 것인지 잘 모르겠습니다.
주요 코드는 아래와 같습니다.
[스레드 선언]
type
TCHThread = class(TThread)
private
i_TH_Ch : smallint;
TH_Rect_1, TH_Rect_2, TH_Rect_3, TH_Rect_4 : tcvRect;
procedure Proc_Disp(i_Ch : smallint);
protected
procedure Execute ; override;
public
constructor Create(i_Thread_Ch: smallint; Rect_1, Rect_2, Rect_3, Rect_4 : tcvRect);
destructor Destroy; override;
end;
[스레드생성]
constructor TCHThread.Create(i_Thread_Ch: smallint; Rect_1, Rect_2, Rect_3, Rect_4 : tcvRect);
begin
inherited Create(True);
i_TH_Ch := i_Thread_Ch;
TH_Rect_1 := Rect_1; TH_Rect_2 := Rect_2; TH_Rect_3 := Rect_3; TH_Rect_4 := Rect_4;
{
TThreadPriority WIN32 스레드 우선 순위 상수 값
tpIdle THREAD_PRIORITY_IDLE -15
tpLowest THREAD_PRIORITY_LOWEST -2
tpLower THREAD_PRIORITY_BELOW_NORMAL -1
tpNomal THREAD_PRIORITY_NOMAL 0
tpHigher THREAD_PRIORITY_ABOVE_NOMAL 1
tpHighest THREAD_PRIORITY_HIGHEST 2
tpTimeCritical THREAD_PRIORITY_TIME_CRITICAL 15
}
Priority := tpLowest; // CPU의 스레드 우선순위 정의(현재로썬 뭘 해도 차이없음)
Self.FreeOnTerminate := False;
Resume;
end;
[스레드실행]
procedure TCHThread.Execute;
var
i : smallint;
Src_Img : pIplImage;
Dest_Img : pIplImage;
P1_Img, P2_Img, P3_Img, P4_Img : pIplImage;
// 회전에 필요
rot_mat: pCvMat;
scale: double;
center: TcvPoint2D32f;
begin
inherited;
i := i_TH_Ch;
while Assigned(FormMain.capture[i]) do
begin
LockValue.Acquire;
try
FormMain.frame[i] := cvQueryFrame(FormMain.capture[i]);
if Assigned(FormMain.frame[i]) then
begin
// 편의상 코드 삭제(영상을 자르고, 회전하고 병합하는 코드)
try
TImage(FormMain.FindComponent('Disp'+IntToStr(i))).Bitmap.Canvas.BeginScene;
finally
IPLImageToFMXBitmap(Dest_Img, TImage(FormMain.FindComponent('Disp'+IntToStr(i))).Bitmap, False);
TImage(FormMain.FindComponent('Disp'+IntToStr(i))).Bitmap.Canvas.EndScene;
end;
cvReleaseImage(Dest_Img);
cvReleaseImage(Src_Img);
end;
finally
LockValue.Release;
end;
end;
end;
[스레드 생성하고 실행부분]
procedure TFormMain.Proc_Set_Display(i_Ch, i_Cam_Idx : Integer);
var
i : Integer;
s_Url : AnsiString;
begin
if i_Cam_Idx > 0 then
begin
중략
capture[i_Ch] := cvCreateFileCapture(PAnsiChar(s_Url));
if Assigned(capture[i_Ch]) then
begin
b_Assign := True;
cvSetCaptureProperty(capture[i_Ch], CV_CAP_PROP_FPS, 15.0);
cvSetCaptureProperty(capture[i_Ch], CV_CAP_PROP_FOURCC, CV_FOURCC('H','2','6','4'));
// ★★★★ 스레드 생성하고 실행(스레드를 배열로 두었습니다) ★★★★★
LockValue := TCriticalSection.Create;
CH_Thread[i_Ch] := TCHThread.Create(i_Ch, Rect_1[i_Ch], Rect_2[i_Ch], Rect_3[i_Ch], Rect_4[i_Ch]);
end;
end;
end;
end;
10개의 영상을 로딩할 경우 CH_Thread[1] ~ CH_Threadp10]으로 10개가 생성되고 서로 다른 영상을 스레드 돌리게됩니다.
실제 procedure TCHThread.Execute; 를 보게되면
while Assigned(FormMain.capture[i]) do 문이 있습니다.
영상이 유효하면 계속 영상의 프레임을 받아서 자르고, 회전하여 TImage에 뿌려주라는 것입니다.
스레드 한번 수행하는데 위처럼 해당 스레드가 계속 반복하도록 만든게 문제일까요?
그렇다면 매번 한프레임 한프레임을 스레드 생성하여 영상을 자르고, 회전하여 TImage에 뿌려주라고 하는게 맞는걸까요?
시스템 자원이 부족한 문제가 아닌데 이렇게 영상이 뚝뚝 끊어지는 이유를 더더욱 모르겠습니다;;
경험 있으신 분께 도움 요청드립니다.
델파이에서의 멀티스레드 구현 방법도 여러가지인거 같은데 어떻게 구현하는 것이 합리적인지 모르겠습니다;;
급해서 관련 문제를 해결하기위해 서핑을 했는데
에서 소개된 AIO - Delphi 용 Coroutine 기반 멀티 스레딩 라이브러리 가 도움이 될까요?
댓글 4
-
김원경
2019.09.02 14:50
-
아크나톤
2019.09.03 01:28
아 Synchronize 메소드로 해결했습니다.. 감사합니다.
큰 차이가 있겠냐 싶었는데 굉장한 차이군요.
감사합니다.
-
happy
2019.09.03 12:04
Synchronize 는 vcl 전체 싱글락이라
Synchronize()가 실행되는 동안 이 함수를 호출하는 나머지 쓰레드들이
작업을 하지 못하고 대기상태에 있어야 합니다.
cpu를 효율적으로 사용하지 못하게 됩니다.
그리고
각기 소스채널에 해당하는 쓰레드가 정해진 채널로 부터
OpenCV에 의해서 버퍼링 되어있는 프레임을 읽는 식으로 코딩되어 있는데
Lock 이 왜 필요 한가요.
무조건 쓰레드 많이 생성한다고 해서 퍼포먼스가 증가하는 건 아닙니다.
쉽게 코딩하기 위해 cvQueryFrame() API를 이용해서 프레임 데이타를 갖고오는 식으로 코딩한거 같은데
쓰레드 풀 몇개 할당해서 소스채널로 부터 각기 채널의 프레임 데이타가 유효한지 체크해서
프레임 버퍼가 유효한 채널만 프레임 데이타를 받아와서 처리하도록 구조를 잡는 게 더 나은 방법 임.
-
아크나톤
2019.09.03 14:45
조언 감사합니다.
영상들은 640x480이며 RTSP로 불러옵니다.
하나의 폼에 최대 16개이고 이것을 10개의 폼에 생성하여 총 160개를 출력하는 것이 목표입니다.
질문에 있는 바와 같이 스펙은 2cpu(총 20코어) + 랩32기가입니다.
처음에 제가 Application.OnIdle안에서 for 루트 돌려서 8의 영상을 받을때까지는 매우 괜찮았습니다.
그런데 12개정도 불러오니 프레임 끊김현상이 발생하지않고 모든 12개 영상의 프레임이 느려지는 현상이 발생하더라구요.
당연히 이 방식은 아닌줄 알고 시도한 것이지만요.
그래서 TThread를 사용해 보았는데 워낙 기본이 안되어있는지라....
원경님께서 말씀하신 방법으로 Synchronize를 사용하니 프레임에는 문제는 없는것 같습니다.
그런데 16개의 영상을 돌리는데 cpu8%로 괜찮은거 같은데 램을 1.4기가정도 먹어버리네요;;;
이 문제도 해결해야할 사항인 것같습니다.
질문을 올릴때 당시 LockValue.Acquire;와 Release등의 코드가 들어있었는데 Sychronize메소드를 사용하면서 모두 제거하였습니다.
말씀하신 쓰레드 풀 할당해서 처리하는 것이 어떤 의미인지 잘 모르겠습니다만 관련하여 구글링해서 한번 해보겠습니다.
조언 감사드립니다.
델파이 응용 프로그램에서 여러 스레드가 실행중인 경우 스레드 실행의 결과로 화면의 사용자 인터페이스를 업데이트하는 경우에는
Synchronize 메소드를 사용합니다.
동기화는 AMethod에 의해 지정된 호출이 기본 스레드를 사용하여 실행되도록하여 다중 스레드 충돌을 방지합니다. 현재 스레드는 AThread 매개 변수에 전달됩니다. 아래 주소를 참조하시면 예문들도 참조하실 수 있습니다.
https://stackoverflow.com/questions/16870387/passing-value-to-synchronize-thread
https://www.thoughtco.com/synchronizing-threads-and-gui-delphi-application-1058159