Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

0과 1 사이

[파이썬] 큐(Queue) 자료구조, 코드 개선 본문

(0, 1)/손마리

[파이썬] 큐(Queue) 자료구조, 코드 개선

고후 2022. 1. 28. 15:03

이전에 잘 동작하던 프로그램이 갑자기

핵심 이미지가 세개인 수어를 인식할 때 매우 버벅거리는 문제가 생겼다.

이에 따라 코드 개선에 도전해봤다.


참고로 우리가 개발중인 손마리 프로그램은
detect한 결과들을 리스트에 저장한 뒤, 해당 리스트를 통해 최종 단어를 출력해주는 프로그램이다.


예를 들면 '쓰러지다1'과 '쓰러지다2'가 차례로 인식된 경우,
['쓰러지다1', '쓰러지다2'] 처럼 리스트에 결과를 저장한 뒤
'쓰러지다'를 출력하도록 코딩하는 것이다.
여기서는 이미지가 1,2로 두개로 나뉘어져있는 경우만 나와있지만
변비1,변비2,변비3 처럼 이미지가 세개인 단어들도 여럿 있다.


그렇다면 리스트에 결과를 저장하는 것은 어떻게 구현할까?
원래의 코드는 아래처럼 효율이 좀 떨어지고 직관적이지 않은 코드였다.
이전 결과들을 pre3_cw, pre2_cw, pre1_cw에 저장하고 현재 결과를 cw에 저장한 뒤
중복되는 값들을 제거해주고.. 다시 리스트에 넣는 코드이다.

참고로 중복 제거를 하는 이유는,
같은 결과들만 여럿 저장되어있으면 쓸모가 없기때문..
이 프로그램은 1초에 20번 이상 detect 하기 때문에
중복 제거를 안하면 리스트에 ‘쓰러지다1’ 같은 값만 100개씩 들어있을 수도 있다.^_^
그래서 다른 결과들만 저장해줘야 한다.

특히 pre_cw들의 중복을 제거해주는 코드가 이해하기 어렵게 쓰여있었고,
우리 팀의 경우에는 이전 결과를 3개만 저장했지만 갑자기 4개까지 저장해야 되는 경우에는 코드를 많이 수정해야할 것이 분명했다.

여긴 괜찮음.

 

그래서. 이전 결과들을 딱 3개만 저장하도록 하고, detect 결과가 null인 경우 저장하지 않고, 중복을 제거할 수 있으면서, 새롭게 detect하는 경우 이전의 결과들이 밀려나도록 하는 자료구조가 필요했다.


사실 직관적으로 생각해봐도 큐이긴 한데, 크기가 고정되어있으면서 바로 직전 결과의 삽입 삭제, 맨 첫 원소의 삭제가 O(1)이어야 한다는 점에서 고민을 좀 했다.
큐의 경우 바로 직전 원소의 삽입 삭제가 효율적이지는 않고, 크기 고정 큐는 딱히 구현해본적이 없기 때문.

그래서 찾아봤더니,
Queue 라이브러리로 구현하는 경우에는
https://docs.python.org/ko/3.7/library/queue.html

from queue import Queue
result_que = Queue(3)
로 구현하는 경우 고정 큐를 구현할 수 있다.

이에 따라 바꾼 고정 큐를 구현한 뒤 직전 결과의 경우에만 before_result에 저장하여 현재 결과와 비교하고,
before_result와 현재 결과가 같은 경우에는 큐에 추가하지 않음으로써
중복을 제거하도록 했다.

그래서 최종적으로 코드를 수정한 결과 32줄의 코드를 줄였고,

20 FPS 미만이었던 속도가 평균 30 FPS, 최대 60 FPS까지 개선되었다!!

FPS

 

수정한 코드


label = "" #detect 결과(실시간으로 detect된 이미지)
word = "" #최종으로 출력할 단어
sentence=[] #출력할 문장

result = "" #현재 결과
before_result = "" #이전 결과
result_que = Queue(3) #result들을 저장하는 큐 생성. 현재 결과까지 최대 3개 저장


while cap.isOpened():

frame_resized = frame_queue.get()
detections = detections_queue.get()
fps = fps_queue.get()

if frame_resized is not None:


label, image = darknet.draw_boxes(detections, frame_resized, class_colors)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#한글을 이미지 위에 출력하기 위해 hand_image로 변환
hand_image = Image.fromarray(image)
draw = ImageDraw.Draw(hand_image)
x, y = 30, 30

#문장 출력
sentence=list(OrderedDict.fromkeys(list(sentence)))
window.sentence.setText(''.join(sentence))

#result가 null이 아닌 경우에만 before_result에 저장
if result != "":
before_result = result

#디텍션 결과가 null이 아닌 경우에만 result에 저장
if label != "":
result = label

#이전 결과와 현재 결과가 다른 경우에만 결과 큐에 저장
if(before_result != result):
if(not result_que.full()):
result_que.put(result)
else:
#큐가 가득 차있으면 원소 제거 후 삽입
result_que.get()
result_que.put(result)


list_of_result = list(result_que.queue)
#큐를 리스트로 변환


#핵심동작 2개,3개인 수화 출력
for i in range(len(list_of_key)):
if list_of_result == list_of_value[i] or list_of_result[1:] == list_of_value[i]:
#현재까지 저장된 result들을 토대로 단어 생성
word = list_of_key[i]
sentence.append(word)
#출력할 문장에 최종 단어 추가
draw.text((x, y), word, font=ImageFont.truetype('malgun.ttf', 36), fill=(0, 0, 0))
break


#detect한 최종 이미지를 UI로 전달
image = np.array(hand_image)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
h,w,c = image.shape
qImg = QtGui.QImage(image.data, w, h, w*c, QtGui.QImage.Format_RGB888)
pixmap = QtGui.QPixmap.fromImage(qImg)
window.image.setPixmap(pixmap)

if cv2.waitKey(fps) == 27:
break
#esc누르면 종료

Comments