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 |
댓글