본문 바로가기

IT/QT

[QT] 2. QT + OPENCV

반응형

☞  메인보드 : Jetson Nano Developer Kit

☞  운영 체제 : Ubuntu18.04 - JetPack 4.4.1

☞  IDE : QtCreator

☞  언어 : C++

 

 

 

 

 

 

 

 

 

 

 

 

19년도 6월인가 그때 처음 OPENCV를 공부했는데 블로그를 시작하고 했던 것을 그냥 올리는게 재미없어져서  올해 3월 이후에는 OPENCV 포스팅을 하지 않았다. 인공지능이나 가상현실처럼 유명한 기술을 맛보기는 해야겠지 싶어서 (왜냐면 요새 기술에는 다 쓰는거 같더라고) 아직 허접한 QT로 편한 UI를 만들어본다는 느낌적인 느낌 느낌으로 시작했다.  그냥 QT를 하기에는 심심하니 가끔 주제를 만들어서 쓸 예정이다.

 

 

 

 

 


※  OPENCV 링크

 

 

QT 프로젝트를 처음 만들면 .pro 파일이 생기는데  맨아래에 OPENCV의 경로를 추가하고 시작하자.

 

INCLUDEPATH 에는 OPENCV 라이브러리의 경로를 LIBS 에는 OPENCV 라이브러리를 링크해야 코드에서 OPENCV 함수를 사용할 수 있다.

 

 

 

 

<INCLUDEPATH>

 

 

 

<LIBS>

 

 

 

 

위 명령어로 찾을 수 있고 만약 저렇게 했는데 링크가 제대로 안됐다는 에러가 발생했다면 그 경로에 가서 심볼릭 링크를 만들어 주면 보통은 해결된다. 

 

sudo ln -s /usr/include/opencv4/opencv2/ /usr/include/opencv2
sudo ln -s [링크하고 싶은 파일이나 디렉터리] [파일 이름]

 

 

 

 

 

 

일단은 이미지를 불러온다.

 

std::string fileName = "/home/yasun95/workspace_qt/cvQt/irene.png";
original = cv::imread(fileName);
cv::cvtColor(original, original, cv::COLOR_BGR2RGB);

 

 

 

파일을 제대로 불러오지 못했을 때 "불러오기 실패" 문구를 반환하는 코드를 넣어주면 좋다고 생각한다.

 

if(!img.data()) { std::cout<< "No Image" << std::endl; }

 

아마 cv::imread 함수로 이미지를 띄우고 나서 해보는 것은 회색화면 만들기 아닐까 싶다.

 

#include <opencv2/imgproc.hpp>
cv::cvtColor(InputArray src, OutputArray dst, int code, int dstCn = 0)
cv::cvtColor(입력 이미지, 결과 이미지, 색 변환 코드, 채널 수)

 

 

이미지는 픽셀로 이루어져있고 (AUTOSIZE로 창을 생성할 경우) 가로 픽셀 수 x 세로 픽셀 수의 사진이 나온다. 나온 이미지는 각 픽셀마다 BGR(BLUE, GREEN, RED) 세 가지의 색의 조합으로 색상이 결정된다. 그래서 index 0, 1, 2 에 B, G, R 세 가지의 정보를 갖고 있고 가로 픽셀 수 x 세로 픽셀 수 만큼의 행렬을 만든다.

 

이렇게 세 가지 색상을 갖고 있는 이미지는 Channel이 3개이고 8bit의 Unsigned char 변수 타입을 갖고 있어 8UC3의 이미지 포맷을 갖고,  회색 이미지의 경우에는 Channel이 1개이고(Black & White) 변수 타입은 그대로여서 8UC1의 이미지 포맷을 갖는다. [이미지 포맷 ☞ OPENCV : BGR , QT의 QImage : RGB]

 

<widget.h>
cv::Mat gray;
cv::Mat resultImage;

<widget.cpp>
cv::cvtColor(original, gray, cv::COLOR_RGB2GRAY);
cv::cvtColor(gray, resultImage, cv::COLOR_GRAY2RGB);
updateImage(resultImage);

 

cv::imread로 불러온 original 이미지는 BGR->RGB 포맷 변환을 마친 상태이다. RGB 포맷의 original 이미지는 색 변환 코드(cv::COLOR_RGB2GRAY)로 회색이되고 이를 결과 이미지 변수인 resultImage에 RGB 포맷으로 넘겨주면 updateImage 함수에 의해 회색 화면이 출력된다.

 

이 GrayScale 변환을 거치고 나면 보통은 blur와 같은 필터링 기법을 익히기 위해 gaussian이나 median, bilateral 등 OPENCV 라이브러리 함수를 사용하지만 이는 OPENCV 탭에서 다뤄보도록 하겠다.

 

 

 

blur 함수들로 필터 효과를 주고나서 차선이나 물체 인식에 사용되는 Canny 함수를 사용한다. Canny는 근접한 픽셀들과의 색을 비교한다. 그리고 입력한 임계값(threshold)에 따라 가장자리라고 추정되는 곳을 흰색 나머지를 검정색으로 표현하는 알고리즘이다. 

 

 

threshold1, threshold2 값을 QSlider와 QSpinBox에서 변경할 수 있다.  회색 이미지 변환과 같은 방식으로 진행되는데 Canny를 사용할 때는 추가적으로 미리 이미지의 Channel을 한 개로 만들어주어야한다. 즉, 원본 이미지가 아닌 회색 이미지로 변환을 한 후에 사용해야한다는 것이다.

 

<widget.h>
cv::Mat gray;
cv::Mat resultImage;

<widget.cpp>
cv::cvtColor(original, gray, cv::COLOR_BGR2GRAY);
cv::Canny(gray, resultImage, lowThreshold, highThreshold);
cv::cvtColor(resultImage, resultImage, cv::COLOR_GRAY2RGB);
updateImage(resultImage);

 

가장자리를 검출 알고리즘에는 Canny 이외에도 Sobel이나 Laplacian 등이 있다. 이또한 OPENCV 탭에서 다루도록 하겠다.

 

 


(전체 코드를 보려면 아래의 '더보기' 클릭)

더보기

1. widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include <QPushButton>
#include <QRadioButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QSlider>
#include <QLabel>
#include <QSpinBox>

#include <opencv4/opencv2/core/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/opencv.hpp>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

signals:
    void valueChanged(int);

public slots:
    void receiveLowValue(int value);
    void receiveHighValue(int value);

    void imgProcessing();

private:
    QPushButton *quitButton;
    QPushButton *inputButton;

    void createRadioGroupBox(const QString &title);
    QGroupBox *radioGroupBox;

    QRadioButton *createRadioButton(const QString &title);
    QButtonGroup *radioGroup;
    QRadioButton *ORIGINAL;
    QRadioButton *GRAY;
    QRadioButton *CANNY;

    void createSliderGroupBox();
    QGroupBox *sliderGroupBox1;
    QGroupBox *sliderGroupBox2;
    void createControls(const QString &title);
    QSlider *slider1;
    QSlider *slider2;

    QGroupBox *controlsGroup;
    QLabel *lowThreshLabel;
    QLabel *highThreshLabel;
    QLabel *valueLabel;

    QSpinBox *lowThreshSpinBox;
    QSpinBox *highThreshSpinBox;
    QSpinBox *valueSpinBox;

    QLabel *screen;

    int lowThreshold = 0;
    int highThreshold = 0;

    void updateImage(cv::Mat img);
    cv::Mat original;
    cv::Mat gray;
    cv::Mat canny;
    cv::Mat resultImage;
};
#endif // WIDGET_H

 

2. main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.resize(600, 600);
    w.show();

    return a.exec();
}

 

 3.widget.cpp

#include "widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    createControls("CANNY CONTROL");
    createRadioGroupBox("IMAGE TYPE");
    createSliderGroupBox();

    screen = new QLabel();
    screen->setPixmap(QPixmap());

    quitButton = new QPushButton("QUIT");
    quitButton->setFixedSize(200,30);
    connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));

    QVBoxLayout *screen_Slider = new QVBoxLayout;
    screen_Slider->setAlignment(Qt::AlignCenter);
    screen_Slider->addWidget(screen);
    screen_Slider->addStretch();
    screen_Slider->addWidget(sliderGroupBox1);
    screen_Slider->addWidget(sliderGroupBox2);

    QVBoxLayout *control_Button = new QVBoxLayout;
    control_Button->addWidget(radioGroupBox);
    control_Button->addWidget(controlsGroup);
    control_Button->addStretch();
    control_Button->addWidget(quitButton);
    control_Button->setAlignment(Qt::AlignCenter);

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addLayout(screen_Slider);
    layout->addStretch();
    layout->addLayout(control_Button);
    setLayout(layout);
    setWindowTitle("IRENE PROCESSING");

    std::string fileName = "/home/yasun95/workspace_qt/cvQt/irene.png";
    original = cv::imread(fileName);
    cv::cvtColor(original, original, cv::COLOR_BGR2RGB);
    imgProcessing();

    connect(slider1, SIGNAL(sliderMoved(int)), lowThreshSpinBox, SLOT(setValue(int)));
    connect(lowThreshSpinBox, SIGNAL(valueChanged(int)), slider1, SLOT(setValue(int)));
    connect(lowThreshSpinBox, SIGNAL(valueChanged(int)), this, SLOT(receiveLowValue(int)));

    connect(slider2, SIGNAL(sliderMoved(int)), highThreshSpinBox, SLOT(setValue(int)));
    connect(highThreshSpinBox, SIGNAL(valueChanged(int)), slider2, SLOT(setValue(int)));
    connect(highThreshSpinBox, SIGNAL(valueChanged(int)), this, SLOT(receiveHighValue(int)));

    connect(lowThreshSpinBox, SIGNAL(valueChanged(int)), this, SLOT(imgProcessing()));
    connect(highThreshSpinBox, SIGNAL(valueChanged(int)), this, SLOT(imgProcessing()));
}

void Widget::receiveLowValue(int value){lowThreshold = value;}
void Widget::receiveHighValue(int value) {highThreshold = value;}

void Widget::updateImage(cv::Mat img){
    if(img.data){
        QImage src(img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
        screen->setPixmap(QPixmap::fromImage(src));
    }
    else{
        screen->setText("NO IMAGE");
    }
}

void Widget::imgProcessing(){
    if(ORIGINAL->isChecked()){
        updateImage(original);
    }
    else if(GRAY->isChecked()){
        cv::cvtColor(original, gray, cv::COLOR_RGB2GRAY);
        cv::cvtColor(gray, resultImage, cv::COLOR_GRAY2RGB);
        updateImage(resultImage);
    }
    else{
        cv::cvtColor(original, gray, cv::COLOR_BGR2GRAY);
        cv::Canny(gray, resultImage, lowThreshold, highThreshold);
        cv::cvtColor(resultImage, resultImage, cv::COLOR_GRAY2RGB);
        updateImage(resultImage);
    }
}

void Widget::createControls(const QString &title){
    controlsGroup = new QGroupBox(title);

    lowThreshLabel = new QLabel("Low Threshold  ");
    highThreshLabel = new QLabel("High Threshold  ");

    lowThreshSpinBox = new QSpinBox;
    lowThreshSpinBox->setSingleStep(1);
    lowThreshSpinBox->setRange(0,200);

    highThreshSpinBox = new QSpinBox;
    highThreshSpinBox->setSingleStep(1);
    highThreshSpinBox->setRange(0,200);

    QGridLayout *controlGridLayout = new QGridLayout;
    controlGridLayout->addWidget(lowThreshLabel,0,0);
    controlGridLayout->addWidget(lowThreshSpinBox,0,1);
    controlGridLayout->addWidget(highThreshLabel,1,0);
    controlGridLayout->addWidget(highThreshSpinBox,1,1);
    controlsGroup->setFixedSize(200,150);
    controlsGroup->setLayout(controlGridLayout);
}

void Widget::createRadioGroupBox(const QString &title){
    radioGroupBox = new QGroupBox(title);

    ORIGINAL = createRadioButton("Original");
    GRAY = createRadioButton("Gray");
    CANNY = createRadioButton("Canny");

    ORIGINAL->setChecked(true);

    QVBoxLayout *imgLayout = new QVBoxLayout;
    imgLayout->addWidget(ORIGINAL);
    imgLayout->addWidget(GRAY);
    imgLayout->addWidget(CANNY);
    radioGroupBox->setFixedSize(200,150);
    radioGroupBox->setLayout(imgLayout);

    connect(ORIGINAL, SIGNAL(clicked()), this, SLOT(imgProcessing()));
    connect(GRAY, SIGNAL(clicked()), this, SLOT(imgProcessing()));
    connect(CANNY, SIGNAL(clicked()), this, SLOT(imgProcessing()));
}

void Widget::createSliderGroupBox(){
    sliderGroupBox1 = new QGroupBox("LOW THRESHOLD");
    slider1 = new QSlider(Qt::Horizontal);
    slider1->setTickPosition(QSlider::TicksBothSides);
    slider1->setTickInterval(50);
    slider1->setSingleStep(1);
    slider1->setValue(0);
    slider1->setMinimum(0);
    slider1->setMaximum(200);

    sliderGroupBox2 = new QGroupBox("HIGH THRESHOLD");
    slider2 = new QSlider(Qt::Horizontal);
    slider2->setTickPosition(QSlider::TicksBothSides);
    slider2->setTickInterval(50);
    slider2->setSingleStep(1);
    slider2->setValue(0);
    slider2->setMinimum(0);
    slider2->setMaximum(200);

    QVBoxLayout *sliderGroup1 = new QVBoxLayout;
    sliderGroup1->addWidget(slider1);
    sliderGroupBox1->setFixedSize(400,50);
    sliderGroupBox1->setLayout(sliderGroup1);

    QVBoxLayout *sliderGroup2 = new QVBoxLayout;
    sliderGroup2->addWidget(slider2);
    sliderGroupBox2->setFixedSize(400,50);
    sliderGroupBox2->setLayout(sliderGroup2);
}

QRadioButton *Widget::createRadioButton(const QString &title){
    QRadioButton *buttonName = new QRadioButton(title);
    return buttonName;
}

Widget::~Widget(){ }

 

 

OpenCV: OpenCV modules

OpenCV  4.2.0 Open Source Computer Vision

docs.opencv.org

함수 매개변수의 출처는 이 곳에서 찾을 수 있다.

 

 

글을 작성하다보니 상당히 QT 중심으로 만들어진 것 같다. 아직은 OPENCV를 접하고나서 초기에 하는 코드를 작성하기 때문에 그런 것이라 생각한다...코드를 작성할 때도 OPENCV에서 문제는 하나도 없었다. QT에서 connect 함수를 제대로 사용하지 못해서 5일간 삽질을 했다. QRadioButton SIGNAL(clicked)에 SLOT(imgProcessing)을 넣지 않아서 RadioButton을 눌러도 이미지 변화가 없었다. QT 구조와 함수에 좀 더 익숙해질 필요가 있음을 느꼈다.

반응형