안녕하세요.
문자열 추출하는 구현은 기본적으로 끝이 났습니다. 그렇지만 성능을 조금 향상하기 위한 기능을 추가해 보도록 하겠습니다. 컴퓨터에 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