본문 바로가기
프로젝트 : EasyBoxer

바운딩박스와 좌표값 추출하기

by Tripleler 2022. 3. 29.

CV 영상인식 프로젝트를 하면서 바운딩박스를 일일이 확인하고 처리하는게 여간 귀찮은 일이 아니었다.

어차피 꽤나 여러 번 이 짓거리를 반복해야 할 것 같아서 라벨링 프로그램을 짜보기로 했다.

 

 

내가 해야할 것은 두 가지다.

1. 바운딩박스를 애플리케이션에 그릴 수 있게 해야한다.

2. 그려진 바운딩박스의 좌표값을 사진의 크기를 기준으로 추출해야 한다.

 

1번 문제는 이미 해결했으니 2번 문제를 해결해보자

 

 

 

 

 

 

 

우선 사진의 크기 좌표를 띄워야 한다.

self.lbl_test.setText(f'{self.img.height()}, {self.img.width()}')
layout.addWidget(self.lbl_test, 0, 80, 5, 20)  # 테스트

문제는 사진의 크기를 처음에 띄우고 나면 사진의 크기가 변해도 텍스트가 변하지 않는다.

 

따라서 사진의 크기가 변경되는 이벤트(resize event)가 발생하면 좌표값도 그에 맞게 변하도록 해주어야 한다.

 

바운딩박스를 표시할 때 처럼 QLabel() 클래스를 상속해서 재 정의하도록 하자.

 

import sys
import cv2
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *


class MyApp(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('EasyBoxer')  # 툴 제목
        self.setGeometry(100, 100, 1000, 350)  # 창의 위치&크기 (x,y,w,h)

        # QWidget과 연결
        self.cent_widget = CentWidget()
        self.setCentralWidget(self.cent_widget.tabs)
        self.show()


class ImgLabel(QLabel):
    resized = pyqtSignal()

    def __init__(self):
        super().__init__()

    def resizeEvent(self, e):
        self.resized.emit()
        return super(ImgLabel, self).resizeEvent(e)


class CentWidget(QWidget):

    def __init__(self):
        super().__init__()

        # 폰트
        font = QFont()
        font.setBold(True)
        font.setPointSize(25)

        # 사진 보여줄 곳
        self.img = ImgLabel()
        self.img.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.img.setScaledContents(True)
        self.img.setPixmap(QPixmap('./icon/flowers.jpg'))
        self.img.resized.connect(self.check_size)

        # 좌표 테스트
        self.lbl_test = QLabel()
        self.lbl_test.setStyleSheet('background-color: #FFFFFF')
        self.lbl_test.setFont(font)

        # 레이아웃
        layout = QGridLayout()
        layout.addWidget(self.img, 0, 0, 80, 80)  # 사진
        layout.addWidget(self.lbl_test, 0, 80, 5, 20)  # 좌표 테스트

        main = QWidget()
        main.setLayout(layout)

        self.tabs = QTabWidget(self)
        self.tabs.setFocusPolicy(Qt.NoFocus)
        self.tabs.addTab(main, 'Main')
        
    def check_size(self):
        self.lbl_test.setText(f'{self.img.width()}, {self.img.height()}')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MyApp()
    sys.exit(app.exec_())

 

resize event가 발생하면 시그널을 방출하도록 ImgLabel() 클래스를 정의했다.

 

그리고 self.img.resized.connect()로 resize이벤트가 발생하면 check_size함수를 호출하도록 했으며,

 

check_size함수는 좌표값을 띄우도록 해주었다.

 

좌표값을 잘 받아온다.

 

 

 

 

 

 

 

이제 바운딩 박스의 좌표를 뽑아내야 한다.

직사각형을 정의하는 방법은 3가지 방법이 존재한다.

 

1. 왼쪽 위 한 점과 오른쪽 아래 한 점의 좌표 (x1, y1, x2, y2)

2. 왼쪽 위 한 점의 좌표와 너비, 높이 (x, y, w, h)

3. 중심 한 점의 좌표와 너비, 높이 (x, y, w, h)

대부분이 1번 방법을 사용하지만 yolo는 3번 방법을 사용한다.

따라서 3번 방법을 표현하도록 구현해야 한다.

 

특히, 어떤 사진이든 왼쪽 위 한 점을 (0, 0)으로, 오른쪽 아래 한 점을 (1, 1)로 포맷해서 사용하기 때문에

아까 사진의 크기값을 받아오도록 먼저 구현한 것이다.

 

class DrawRectangle(QLabel):
    coordinate = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self.begin = QPoint()
        self.destination = QPoint()

    def paintEvent(self, e):
        painter = QPainter(self)
        painter.setPen(QPen(Qt.red, 3))
        rect = QRect(self.begin, self.destination)
        painter.drawRect(rect.normalized())

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.begin = e.pos()
            self.destination = self.begin
            self.update()

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.LeftButton:
            self.destination = e.pos()
            self.update()

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.destination = e.pos()
            self.update()
            coord_list = [self.begin.x(), self.begin.y(), self.destination.x(), self.destination.y()]
            self.coordinate.emit(coord_list)

마우스클릭을 하고 손에서 떼는 이벤트(mouseReleaseEvent)가 발생하면 현재 좌표 시그널을 방출하도록 구현했다.

 

self.lbl_rect.coordinate.connect(self.coordinate)

이후 시그널이 방출되면 coordinate함수를 호출하도록 했다.

def coordinate(self, coordinate):
    x1, y1, x2, y2 = coordinate
    width, height = self.img.width(), self.img.height()
    x = round((x1 + x2) / 2 / width, 4)
    y = round((y1 + y2) / 2 / height, 4)
    w = round(abs(x1 - x2) / width, 4)
    h = round(abs(y1 - y2) / height, 4)
    self.lbl_test2.setText(f'x:{str(x)}\ny:{str(y)}\nw:{str(w)}\nh:{str(h)}')

coordinate함수는 기존좌표를 yolo용 좌표료 변환하고, 이를 표시하도록 했다.

잘 작동하는 것을 볼 수 있다.

 

'프로젝트 : EasyBoxer' 카테고리의 다른 글

EasyBoxer 업로드  (0) 2022.04.29
EasyBoxer 제작기 중간점검  (0) 2022.04.04

댓글