Skip to content

Ground Station Algorithm Test

dazory edited this page Jul 4, 2021 · 8 revisions

Algorithm Test

File Tree

├─ diva2
│  ├─ build
│  │      └─ GroundStation
│  │            ├─ AlgorithmTesting
│  │            └─ AlgorithmTesting_GUI
│  │                 └─ diva_algorithm 
│  ├─ GroundStation
│  │      ├─ AlgorithmTesting
│  │      │         ├─ lane_detection_algorithm
│  │      │         └─ obj_detection
│  │      │             └─ darknet
│  │      └─ AlgorithmTesting_GUI
...

darknet파일은 github에 업로드되지않아 별도로 다운로드받아 디렉토리에 추가해야한다. 여기에서 다운로드받을 수 있다.

AlgorithmTesting_GUI

build하기 전에 diva2/GroundStation/AlgorithmTesting_GUI/ModelRunThread.cpp에서 다음의 절대경로를 본인의 경로로 수정해주어야 한다.

    if(sensorIdx == Sensor::cam){
        if(algorithmIdx==0) // lane detection
        {
            command += "cd /home/diva2/diva2/GroundStation/AlgorithmTesting/lane_detection_algorithm/ ";
            ... 
        }else if(algorithmIdx==1) // object detection
        {
            command += "cd /home/diva2/diva2/GroundStation/obj_detection/darknet/ ";
            ... 

Qt를 이용하여 프로그래밍한 AlgorithmTesting의 GUI에 관한 파일이 들어있다. build에서 make를 통해 build/GroundStation/AlgorithmTesting_GUI/diva_algorithm 실행파일을 생성할 수 있다. ./diva_algorithm 명령어를 통해 GUI를 실행할 수 있으며 실행 결과는 아래와 같다. diva_algorithm

CAM sensor data에 대해 lane detection과 object detection 알고리즘만을 지원한다.

Test

lane detection 및 object detection 알고리즘 테스트를 위한 data, config, weights파일과 images 파일은 algorithm_resources에서 다운로드받을 수 있다.

lane detection는 다음과 같은 세팅을 통해 테스트를 실행할 수 있다.

  • data file : diva2/storage/lane_detection/fcn_big_01.json
  • config file : -
  • weight file : diva2/storage/lane_detection/fcn_big_01.h5
  • input file : diva2/storage/lane_detection/images

알고리즘의 제약으로 인해 input file이 존재하는 디렉토리의 상위 디렉토리 내에 result directory가 존재해야 core dumped error를 방지할 수 있다. 또한 상위디렉토리의 상위 디렉토리 내에 역시 result directory가 존재하고, 그 안에 result.txt 파일이 존재해야 한다. 가령 아래와 같이 directory를 구성할 수 있다.

├─ user
│  ├─ algorithm_resources
│  │      ├─ lane_detection
│  │      ├─ object_detection
│  │      │     ├─ obj.data
│  │      │     ├─ cp.cfg
│  │      │     ├─ cp_best.weights
│  │      │     └─ result ◂─────────── ★very important★
│  │      └─ result ◂───────────────── ★very important★
│  │            └─ result.txt ◂─────── ★very important★

이러한 디렉토리구조를 무시하는 경우 core dumped가 발생할 수 있다.


object detection을 수행하기 위한 파일은 [diva2/storage/object_detection]에 들어있다. 다음과 같은 세팅을 통해 테스트를 실행할 수 있다.

  • data file : diva2/storage/object_detection/obj.data
  • config file : diva2/storage/object_detection/cp.cfg
  • weight file : cp_best.weights
  • input file : 입력 이미지가 들어있는 디렉토리

object detection은 darknet 파일을 통해 실행된다. darknet파일은 diva2/GroundStation/AlgorithmTesting/obj_detection/darknet에서 다음의 명령어를 통해 얻어진다.

cmake .
make

CUDA가 없다면 cmake . -DENABLE_CUDA=OFF으로 대체하면 된다.

어떤 PC에서는 build 안에서 cmake ..가 안되므로 darknet 디렉토리에서 cmake .를 해주어야 한다. 하지만 또 어떤 PC에서는 mkdir build && cd build && cmake .. && make가 가능하다.

make를 수행하면 분명 error가 날 것이다. error때문에 완전히 make가 되지 않더라도 [ 98%] Linking CXX executable darknet만 되면 사용가능하다. 이 error로인해 root의 CMakeLists.txt와 darknet의 CMakeLists.txt를 연결하지 않았다.




동작 방식

1. mainwindow의 play button (link)

void MainWindow::on_pb_Play_clicked()
{
    printf("[MainWindow::on_pb_Play_clicked] start\n");

    // [ Set the Environments ]
    // < Set Algorithm Thread to Run >
    sensorIdx = 1;
    algorithmIdx = 1;

    algorithmThread = new AlgorithmThread(this);
    algorithmThread->set_sensorIdx(sensorIdx);
    algorithmThread->set_algorithmIdx(algorithmIdx);
    algorithmThread->set_input_path(this->input_path.path().toStdString());
    algorithmThread->start();

    modelRunThread = new ModelRunThread(this);
    modelRunThread->set_sensorIdx(sensorIdx);
    modelRunThread->set_algorithmIdx(algorithmIdx);
    modelRunThread->set_datafile(this->data_path.path().toStdString());
    modelRunThread->set_configfile(this->config_path.path().toStdString());
    modelRunThread->set_weightfile(this->weight_path.path().toStdString());
    modelRunThread->start();

    connect(algorithmThread, SIGNAL(send_qimage(QImage, QImage, QString)), this, SLOT(display_original(QImage, QImage, QString)));

...

}

mainwindow의 play button이 눌리면 algorithmThread와 modelRunThread의 멤버변수값이 세팅이 되며 thread가 실행된다.


2. AlgorithmThread의 동작 (link)
AlgorithmThread는 modelRunThread의 결과를 수신하여 Qt GUI에 출력한다.

void AlgorithmThread::run(){

    ...
    
    if((sensorIdx==Sensor::cam) && (algorithmIdx==0)){
        lane_detection();
    }else if((sensorIdx==Sensor::cam) && (algorithmIdx==1)){
        obj_detection();
    }
    
    ...

}

멤버변수 세팅값(센서, 알고리즘 번호)에 따라 각 알고리즘이 동작한다.

2.1. lane_detection

① zmq 연결

zmq::context_t ctx(1);
zmq::socket_t algorithmTesting_sub(ctx, ZMQ_SUB);
algorithmTesting_sub.connect("tcp://localhost:9899");
algorithmTesting_sub.setsockopt(ZMQ_SUBSCRIBE, "", 0);
printf("[AlgorithmThread::lane_detection] connect\n");

② sub socket으로부터 데이터 읽기

algorithmTesting_sub.recv(&msg);
frame_cnt++;

③ protobuf image로 parsing하기

std::string _buff = std::string(static_cast<char *>(msg.data()), msg.size());
algo_img.ParseFromString(_buff);

④ fps 계산하기

float millis = algo_img.millis_term();
millis_sum += millis;
float fps = (float)(1000*frame_cnt)/(float)(millis_sum);
printf("fps=%f\n", fps);

⑤ Qt GUI에 출력하기

// [ Preprocessing for Visualization ]
// < Setting for original image >
memcpy((void*)frame_original.data, (void*)(&algo_img.image_original()[0]), frame_original.step[0]*(size_t)frame_original.rows);

// < Setting for result image >
memcpy((void*)frame_result.data, (void*)(&algo_img.image_result()[0]), frame_result.step[0]*(size_t)frame_result.rows);

// [ Visualization at Qt GUI ]
QImage image_original(frame_original.size().width, frame_original.size().height, QImage::Format_RGB888);
memcpy(image_original.scanLine(0), frame_original.data, static_cast<size_t>(image_original.width() * image_original.height() * frame_original.channels()));
        
QImage image_result(frame_result.size().width, frame_result.size().height, QImage::Format_RGB888);
memcpy(image_result.scanLine(0), frame_result.data, static_cast<size_t>(image_result.width() * image_result.height() * frame_result.channels()));
        
emit send_qimage(image_original, image_result, QString::number(fps)); 

2.2. object_detection
object_detection에 사용되는 darknet은 C기반으로 작성되었으므로 zmq.hpp가 아닌 zmq.h 라이브러리를 이용하여 코드를 작성해야 한다.

① zmq 연결하기

// [ Setting ]
// < Zeromq >
void *ctx = zmq_ctx_new();
void *socketSub = zmq_socket(ctx, ZMQ_REP);
int rc = -1;
rc = zmq_connect(socketSub, "tcp://localhost:9899");
printf("[AlgorithmThread::obj_detection] connect:%d\n", rc);

이유는 모르겠으나 PUB-SUB 소켓은 사용이 불가능했다. 따라서 REP-REQ 소켓을 이용하여 프로토콜 규칙을 만들어 통신을 수행하였다.

② directory로부터 input image file path 읽기

    // < Input File >
    printf("[AlgorithmThread::obj_detection] get input files\n");
    vector<string> fileName;
    DIR *d = opendir(get_input_path().c_str());
    struct dirent *dir;
    vector<string> fileList;
    int i=0;
    if (d){
        while ((dir = readdir(d)) != NULL){
            string d_name = dir->d_name;
            if((d_name.compare("..")==0) || (d_name.compare(".")==0))
                continue;
            i++;
            fileList.push_back(d_name);
        }
        closedir(d);
    }

이를 통해 fileList에 input image file의 path 정보가 저장된다.

③ REP 소켓으로부터 start signal을 수신한 뒤, REP소켓으로 fileList 내용을 차례로 송신

    // [PROTOCOL0: START SIGNAL]
    char buf [512];
    int nbytes = zmq_recv (socketSub, buf, 512, 0);
    assert (nbytes != -1);

    ...

    // [ Communicate ]
    // < Send a file name of original image >
    len = sprintf(buf, "%s/%s",get_input_path().c_str(), fileList[count].c_str());
    buf[len] = '\0';
    rc = zmq_send(socketSub, buf, len+1, 0);
    assert (rc > 0);
    fn = buf; // file name = buf

④ REP소켓으로부터 결과 이미지의 경로명을 수신

    // < Receive a file name of result image >
    nbytes = zmq_recv (socketSub, buf, 512, 0);
    assert (nbytes != -1);
    frame_cnt++;

⑤ Qt GUI에 출력하기

mat = cv::imread(fn);
mat = cv::imread(result_filename);

...

emit send_qimage(image_original, image_result, QString::number(fps));

...

input file path인 fn와 result file path인 result_filename을 이용하여 이미지를 불러온 뒤, Qt GUI에 출력한다.

3. modelRunThread의 동작 (link)
modelRunThread는 알고리즘을 실행하여 algorithmThread에 그 결과를 전송한다.

    void ModelRunThread::run(){
    
    string command;
    if(sensorIdx == Sensor::cam){
        if(algorithmIdx==0) // lane detection
        {
            command += "cd /home/diva2/diva2/GroundStation/AlgorithmTesting/Algorithm/ ";
            command += "&& python3 model.py ";
        }else if(algorithmIdx==1) // object detection
        {
            command += "cd /home/diva2/diva2/GroundStation/obj_detection/darknet/ ";
            command += "&& ./darknet detector test ";
            command += this->str_datafile;
            command +=" ";
            command += this->str_configfile;
            command +=" ";
            command += this->str_weightfile;
        }
    }
    
    int ret = system(command.c_str());

}

system command를 이용하여 파라미터 세팅 및 알고리즘을 실행한다.

References

2.1. lane_detection lane detection 알고리즘은 BuiKhoi/DigitalRace2019을 참고하여 작성했다.

2.2. object_detection lane detection 알고리즘은 BuiKhoi/DigitalRace2019을 참고하여 작성했다.