행복한 하루
[도서 실습] Qt 5 and OpenCV 4 Computer Vision (Literacy – EAST detector와 tesseract과 이용한 text 추출 + 스크린 캡처) with Raspberry Pi 본문
[도서 실습] Qt 5 and OpenCV 4 Computer Vision (Literacy – EAST detector와 tesseract과 이용한 text 추출 + 스크린 캡처) with Raspberry Pi
변화의 물결 2022. 7. 16. 00:03
안녕하세요.
문자열 추출하는 구현은 기본적으로 끝이 났습니다. 그렇지만 성능을 조금 향상하기 위한 기능을 추가해보도록 하겠습니다. 컴퓨터에 desktop 화면을 드래그 선택해서 캡쳐 이미지에서 문자를 추출하는 기능을 추가해 볼 예정입니다.
이전 내용 처럼 frozen_east_text_detection.pb 파일은 디버그 디렉터리에 있어야 합니다.
1. ScreenCapturer 클래스 생성
- QtCreator에서 LiteracyW 프로젝트를 불러온 후 프로젝트 파일에서 오른쪽 버튼을 눌러 “Add New”를 눌러 C++ Class를 생성합니다.
- 클래스 이름을 ScreenCapturer로 하고 base class를 QWidget으로 선택해서 생성합니다. 그러면 header 파일과 Source 파일이 생성된 것을 확인할 수 있습니다.
2. ScreenCapturer.h 작성하기
- MainWindow *window는 캡처한 영역을 이미지로 지정해서 MainWindow의 showImage로 전달하기 위해서 포인터 선언
- QPixmap screen 은 화면을 저장하기 위한 변수
- QPoint p1, p2은 선택한 영역의 오른쪽 상단, 왼쪽 하단의 지점
- mouseDown는 마우스 클릭하여 드래그를 확인하기 위한 플래그 변수
- ScreenCapturer.h 에 #include "mainwindow.h" 추가해주어야 MainWindow 에러가 발생하지 않음
class ScreenCapturer : public QWidget {
Q_OBJECT
public:
explicit ScreenCapturer(MainWindow *w);
~ScreenCapturer();
protected:
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private slots:
void closeMe();
void confirmCapture();
private:
void initShortcuts();
QPixmap captureDesktop();
private:
MainWindow *window;
QPixmap screen;
QPoint p1, p2;
bool mouseDown;
};
3. ScreenCapturer.cpp 작성하기
1) 생성자
- widget의 테두리가 없고, 가장 상단에 나타날 수 있는 옵션 선택한 후 생성합니다. 그리고 각종 버튼을 제거하고 전체 화면으로 사이즈를 변경하고 단축키(hotkey)를 설정합니다.
ScreenCapturer::ScreenCapturer(MainWindow *w):
QWidget(nullptr), window(w)
{
setWindowFlags(
Qt::BypassWindowManagerHint
| Qt::WindowStaysOnTopHint
| Qt::FramelessWindowHint
| Qt::Tool
);
setAttribute(Qt::WA_DeleteOnClose);
screen = captureDesktop();
resize(screen.size());
initShortcuts();
}
2) 화면 조정
- 한대의 컴퓨터에 여러 화면으로 사용할 수 있고 QGuiApplication::screens()로 모든화면을 얻어 연결합니다.
그리고 QApplication::desktop()->winId()를 사용하여 데스크톱 위젯(루트 창)의 ID를 얻고 데스크탑 루트 창을 QPixmap의 인스턴스로 가져옵니다.
- 선택한 사각형의 위치와 크기를 grabWIndow 함수에 전달하기 때문에 모든 화면을 포함한 전체 데스크톱이 잡힙니다. 마지막으로 이미지의 픽셀 비율을 로컬 장치에 맞게 적절한 비율로 설정한 전달 합니다.
QPixmap ScreenCapturer::captureDesktop() {
QRect geometry;
for (QScreen *const screen : QGuiApplication::screens()) {
geometry = geometry.united(screen->geometry());
}
QPixmap pixmap(QApplication::primaryScreen()->grabWindow(
QApplication::desktop()->winId(),
geometry.x(),
geometry.y(),
geometry.width(),
geometry.height()
));
pixmap.setDevicePixelRatio(QApplication::desktop()->devicePixelRatio());
return pixmap;
}
3) 마우스 이벤트
- 캡처가 시작되고 마우스가 클릭되었을 때 좌표를 저장하고 화면의 변화를 갱신하기 update() 호출합니다. 그리고 마우스 드래그 상태를 Flag 변수로 확인하고 마우스 클릭 후 좌표를 저장합니다.
void ScreenCapturer::mousePressEvent(QMouseEvent *event)
{
mouseDown = true;
p1 = event->pos();
p2 = event->pos();
update();
}
void ScreenCapturer::mouseMoveEvent(QMouseEvent *event)
{
if(!mouseDown) return;
p2 = event->pos();
update();
}
void ScreenCapturer::mouseReleaseEvent(QMouseEvent *event)
{
mouseDown = false;
p2 = event->pos();
update();
}
4) paintEvent override
- update() 함수가 실행될 때 혹은 widget이 크기 등 변경이 일어나면 실행됩니다.
- 캡처된 이미지(desktop 배경)를 위젯에 그려주고, 캡처된 의미로 약간의 회색이 생기게 하고, 마우스로 선택된 영역은 원래 색으로 만들어줍니다.
void ScreenCapturer::paintEvent(QPaintEvent*) {
QPainter painter(this);
painter.drawPixmap(0, 0, screen);
QRegion grey(rect());
if(p1.x() != p2.x() && p1.y() != p2.y()) {
painter.setPen(QColor(200, 100, 50, 255));
painter.drawRect(QRect(p1, p2));
grey = grey.subtracted(QRect(p1, p2));
}
painter.setClipRegion(grey);
QColor overlayColor(20, 20, 20, 50);
painter.fillRect(rect(), overlayColor);
painter.setClipRect(rect());
}
5) 이미지 저장과 종료
- 마우스로 선택한 영역의 그림을 복사해서 메인 윈도우(Widget)에 전달해주는 함수와 캡처하는 위젯을 종료하면서 메인 위젯을 활성화시키는 함수
void ScreenCapturer::confirmCapture()
{
QPixmap image = screen.copy(QRect(p1, p2));
window->showImage(image);
closeMe();
}
void ScreenCapturer::closeMe()
{
this->close();
window->showNormal();
window->activateWindow();
}
6) 단축키 설정
- ENTER키와 ESC키를 이미지 저장과 캡처 위젯 종료 함수와 연결합니다.
void ScreenCapturer::initShortcuts() {
new QShortcut(Qt::Key_Escape, this, SLOT(closeMe()));
new QShortcut(Qt::Key_Return, this, SLOT(confirmCapture()));
}
4. MainWindow.h 수정하기
- ToolBar에 기능을 추가하기 위해, 변수를 만들고 Pixmap 이미지를 불러오기 showImage 함수 등을 선언합니다.
class MainWindow : public QMainWindow
{
// ...
public:
// ...
void showImage(QPixmap);
// ...
private slots:
// ...
void captureScreen();
void startCapture();
private:
// ..
QAction *captureAction;
// ...
};
5. MainWindow.cpp 수정하기
- createActions() 함수 내에 액션을 하나 추가하고 캡처 함수와 연결해줍니다.
captureAction = new QAction("Capture Screen", this);
fileToolBar->addAction(captureAction);
// ...
connect(captureAction, SIGNAL(triggered(bool)), this, SLOT(captureScreen()));
- 캡처하는 함수는 메인 윈도우를 최소화시키고, 타이머와 캡처 위젯을 생성하는 함수와 연결합니다.
- 500ms 후에 한 번만 실행되도록 합니다.
void MainWindow::captureScreen()
{
this->setWindowState(this->windowState() | Qt::WindowMinimized);
QTimer::singleShot(500, this, SLOT(startCapture()));
}
- 캡처하는 위젯을 생성하고 활성화시켜 캡처 로직이 실행되게 합니다.
void MainWindow::startCapture()
{
ScreenCapturer *cap = new ScreenCapturer(this);
cap->show();
cap->activateWindow();
}
6. LiteracyW.pro 확인
- QtCreator를 통해서 ScreenCapturer 클래스를 생성했다면 자동으로 헤더 파일과 소스파일 이름이 추가되어 있지만, 수동으로 생성했다면 추가해주어야 합니다.
HEADERS += mainwindow.h screencapturer.h
SOURCES += main.cpp mainwindow.cpp screencapturer.cpp
7. 실행 결과
- Capture Screen 버턴을 누르면 캡처 위젯이 실행되어 화면 색이 살짝 회색으로 변경되고, 마우스로 드래그하면 선택된 영역의 색상이 변화되는 것을 확인할 수 있습니다.
- 영역을 선택 후 OCR 버튼을 눌러 글자가 추출되는 것을 확인할 수 있습니다.
- 많은 글자를 추출하고 있지만 이상한 기호로 인식하는 것들도 있기 때문에 좀 더 최신의 OCR 라이브러리 혹은 학습 데이터가 필요해 보입니다.
감사합니다.
<참고 사이트>
1. Qt 5 and OpenCV 4 Computer Vision github
https://github.com/PacktPublishing/Qt-5-and-OpenCV-4-Computer-Vision-Projects
2. 멤버 함수 문서
https://runebook.dev/ko/docs/qt/qscreen?page=2#grabWindow