[7] Nginx 서버 셋업 - Drogon 서버 OpenCV를 활용한 Text Detection API 구축

RHEL 환경에서 Text Detection을 Drogon 서버에 연동하여 API 형태로 추출 및 Client에 보여지는 기능 개발

주의. OCR(Optical Character Recognition, 광학 문자 인식)이 아닌 Text Detection이 목적 Detection 이후 OCR은 추후에 연동 해보는걸로..

0. Text Detection 프레임 워크 서치

프레임워크 언어 장점 단점
Tesseract OCR C++ (Python 등) 오픈 소스, 다국어 지원, 커스터마이징 가능 전처리 없이는 성능 저하 가능, 텍스트 감지 기능이 따로 없으므로 OpenCV 등과 함께 사용 필요
OpenCV + EAST 모델 C++, Python 빠른 텍스트 감지, CPU 및 GPU 모두 지원, OpenCV와의 통합 용이 텍스트 감지만 지원하므로 OCR은 별도 필요, 고해상도 이미지 처리 시 성능에 제약 가능
EasyOCR Python PyTorch 기반으로 다양한 언어 및 스타일 지원, 설치 및 사용 용이 CPU 환경에서는 느릴 수 있음, 고성능 처리에는 GPU가 필요
Amazon Textract (API) 다중 언어 지원 AWS 서버리스 배포 가능, 고급 기능 (테이블 및 양식 인식), 안정적인 성능 대용량 데이터 처리 시 비용 발생, 클라우드 외 사용 불가 및 보안 고려 필요
Google Vision API (API) 다중 언어 지원 다양한 텍스트 감지 및 이미지 분석 기능, GCP 서버리스 배포 가능 대용량 처리 시 비용 발생, 클라우드 외 사용 불가 및 민감 데이터의 보안 문제
PaddleOCR Python 아시아 언어에 뛰어난 성능, 경량화된 모델로 CPU에서도 빠른 처리 가능 PaddlePaddle 기반으로 PyTorch/TensorFlow 사용자에겐 진입 장벽, Python 기반으로 타 언어와 통합 필요
  • 외부 API를 호출하는것은 제외한다.
  • Detection 목적이기에 OpenCV + EAST 모델로 진행한다
  • API 기능은 이미지 내 Text의 좌표 정보와, 언어검출 문자에 프레임을 씌워 새 이미지 생성 ($filename_detection.jpg)

1. OpenCV 셋업 및 필수 Library 설치

아래 형식으로 진행 ( shell 로 우선 구현함 )

#!/bin/sh

sudo yum update -y
sudo yum groupinstall -y "Development Tools"
sudo yum install -y epel-release
sudo yum install -y cmake git gtk2-devel boost-devel
sudo yum install -y libjpeg-turbo-devel libpng-devel libtiff-devel
sudo yum install -y libdc1394-devel gstreamer1-devel gstreamer1-plugins-base-devel
sudo yum install -y tbb-devel

# OpenCV 4.10.0
wget https://github.com/opencv/opencv/archive/refs/tags/4.10.0.tar.gz
mv 4.10.0.tar.gz opencv.tar
tar -xvf opencv.tar
mv opencv-4.10.0 opencv

# OpenCV Contrib 4.10.0
wget https://github.com/opencv/opencv_contrib/archive/refs/tags/4.10.0.tar.gz
mv 4.10.0.tar.gz opencv_contrib.tar
tar -xvf opencv_contrib.tar
mv opencv_contrib-4.10.0 opencv_contrib

# OpenCV Build
cd ./opencv
mkdir build
cd build

# CMake
cmake -DOPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
      -DCMAKE_BUILD_TYPE=Release \
      -DBUILD_SHARED_LIBS=ON \
      -DWITH_IPP=ON \
      -DWITH_TBB=ON \
      -DWITH_OPENMP=ON \
      -DENABLE_FAST_MATH=ON \
      -DCMAKE_INSTALL_PREFIX=/usr/local \
      -DBUILD_EXAMPLES=OFF \
      -DBUILD_TESTS=OFF \
      -DBUILD_PERF_TESTS=OFF \
          ..

# 빌드 및 설치
make -j$(nproc)  # 시스템의 모든 CPU 코어를 사용하여 빌드
sudo make install
sudo ldconfig  # 라이브러리 캐시 업데이트

시스템 경로에 우선 설치하도록 진행함


2. API 개발

이전에서 하던 서버에 계속 이어서 진행한다.

2-1. Drogon Ctl 연결

cd /root/drogon2/drogon/build/drogon_ctl 
drogon_ctl create controller TextDetectionController

2-2. TextDetection 구현 소스 위치할 경로 ( /root/drogon2/drogon/build/drogon_ctl/testAPI/controllers )

TextDetectionController.h

#pragma once
#include 
#include 
#include 

using namespace drogon;

void initializeModel();

class TextDetectionController : public HttpController
{
public:
    std:: string _storagePath = "/root/storage/";
    METHOD_LIST_BEGIN
    // src에 있는 이밎 파일을 text detection 하여 dst로
    ADD_METHOD_TO(TextDetectionController::handleTextDetection, "/text-detection", Get);
    METHOD_LIST_END

    // 메서드 선언
    void handleTextDetection(const HttpRequestPtr& req, std::function&& callback);
    std::vector decodeBoundingBoxes(const cv::Mat& scores, const cv::Mat& geometry, float scoreThresh);
};

TextDetectionController.cc

#include "TextDetectionController.h"
#include 
#include 

using namespace cv;
using namespace cv::dnn;
namespace {
    cv::dnn::Net eastNet;
    const std::string eastModelPath = "./frozen_east_text_detection.pb";
}

// OpenCV Initialize
// Model Init이 시간이 걸리기에, 전역으로 우선 뺏다
// 이건 thread unsafe 하기 때문에, 서버 로직으로는 개선이 필요하다
void initializeModel() {
    if (eastNet.empty()) {
        eastNet = cv::dnn::readNet(eastModelPath);
        if (eastNet.empty()) {
            throw std::runtime_error("Failed to load EAST model");
        }
    }
}


std::vector TextDetectionController::decodeBoundingBoxes(const cv::Mat& scores, const cv::Mat& geometry, float scoreThresh)
{
    std::vector detections;
    const int numRows = scores.size[2];
    const int numCols = scores.size[3];

    for (int y = 0; y < numRows; y++) {
        const float* scoresData = scores.ptr(0, 0, y);
        const float* x0_data = geometry.ptr(0, 0, y);
        const float* x1_data = geometry.ptr(0, 1, y);
        const float* x2_data = geometry.ptr(0, 2, y);
        const float* x3_data = geometry.ptr(0, 3, y);
        const float* anglesData = geometry.ptr(0, 4, y);

        for (int x = 0; x < numCols; x++) {
            float score = scoresData[x];
            if (score < scoreThresh)
                continue;

            float offsetX = x * 4.0;
            float offsetY = y * 4.0;
            float angle = anglesData[x];
            float cosA = cos(angle);
            float sinA = sin(angle);
            float h = x0_data[x] + x2_data[x];
            float w = x1_data[x] + x3_data[x];

            Point2f offset(offsetX + cosA * x1_data[x] + sinA * x2_data[x],
                           offsetY - sinA * x1_data[x] + cosA * x2_data[x]);
            Point2f p1 = Point2f(-sinA * h, -cosA * h) + offset;
            Point2f p3 = Point2f(-cosA * w, sinA * w) + offset;
            RotatedRect rrect(0.5f * (p1 + p3), Size2f(w, h), -angle * 180.0f / CV_PI);
            detections.push_back(rrect);
        }
    }

    return detections;
}

void TextDetectionController::handleTextDetection(const HttpRequestPtr& req, std::function&& callback)
{
    Json::Value respStr;
    HttpStatusCode code = k200OK;

    do
    {
        auto filename = req->getParameter("filename");
        if (filename.empty())
        {
            code = k404NotFound;
            break;
        }

        std::string path = _storagePath.c_str() + filename;
        // OpenCV Read Image From Server Local
        Mat image = imread(path);
        if (image.empty())
        {
            code = k404NotFound;
            break ;
        }

        // 원본 이미지 크기와 변환 비율 설정
        int origH = image.rows;
        int origW = image.cols;
        int newW = 320;
        int newH = 320;
        float rW = static_cast(origW) / newW;
        float rH = static_cast(origH) / newH;

        // blob 생성 및 네트워크 입력 설정
        Mat blob = blobFromImage(image, 1.0, Size(newW, newH), Scalar(123.68, 116.78, 103.94), true, false);
        eastNet.setInput(blob);

        // EAST 모델의 출력 레이어 설정
        std::vector outputLayers = {"feature_fusion/Conv_7/Sigmoid", "feature_fusion/concat_3"};
        std::vector outs;
        eastNet.forward(outs, outputLayers);

        // 텍스트 감지 수행
        std::vector detections = decodeBoundingBoxes(outs[0], outs[1], 0.7);
        if ( detections.size() <= 0 )
        {
            // Detection 된 텍스트 없으면 404 return
            code = k404NotFound;
            break;
        }

        // JSON 형식으로 결과 반환
        Json::Value jsonResponse;
        for (const auto& detection : detections) {
            Rect boundingBox = detection.boundingRect();
            boundingBox.x *= rW;
            boundingBox.y *= rH;
            boundingBox.width *= rW;
            boundingBox.height *= rH;

            Json::Value box;
            box["x"] = boundingBox.x;
            box["y"] = boundingBox.y;
            box["width"] = boundingBox.width;
            box["height"] = boundingBox.height;
            jsonResponse["detections"].append(box);
        }

        // 응답 생성
        auto response = HttpResponse::newHttpJsonResponse(jsonResponse);

        // Text 영역 사각형으로 처리
        // 감지된 텍스트 영역에 사각형 그리기
        for (const auto& detection : detections)
        {
            Rect boundingBox = detection.boundingRect();
            boundingBox.x *= rW;
            boundingBox.y *= rH;
            boundingBox.width *= rW;
            boundingBox.height *= rH;

            rectangle(image, boundingBox, Scalar(0, 255, 0), 2);  // 녹색 사각형 그리기
        }
        // 이미지 저장
        std::filesystem::path outputPath = _storagePath + (std::filesystem::path(path).stem().string() + "_detection.jpg");
        imwrite(outputPath.string(), image);

        callback(response);
        return ;
    }
    while(false);


    auto resp = HttpResponse::newHttpResponse();
    resp->setStatusCode(code);
    callback(resp);
}



main.cc

#include 
#include "controllers/TextDetectionController.h"

int main() {
    try
    {
        // 서버 초기화 시 모델 로드
        initializeModel();
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error initializing model: "
                << e.what() << std::endl;
        return 1;  // 모델 로드에 실패하면 서버 실행 중단
    }
    drogon::app().loadConfigFile("../config.json");

    LOG_INFO << "Server RUN";
    drogon::app().run();

    return 0;
}

2-3. drogon CMake 수정

cd /root/drogon2/drogon/build/drogon_ctl/testAPI
vi CMakeLists.txt
  • 아래 형태로, 이전에 생성한 FileController.cc 뒤에 TextDetectionController.cc를 쓴다
작업필요 사항
# find_package(OpenCV CONFIG REQUIRED) 추가
# target_link_libraries에 ${OpenCV_LIBS} 추가

# OpenCV 경로가 기본 위치가 아닌 경우 경로 지정 (위 내용대로 햇으면 추가할 필요는 없음)
# set(OpenCV_DIR "/path/to/opencv")  # 예: /usr/local/opencv
---

add_executable(${PROJECT_NAME} main.cc controllers/FileController.cc controllers/TextDetectionController.cc)

# ##############################################################################
# If you include the drogon source code locally in your project, use this method
# to add drogon
# add_subdirectory(drogon)
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
#
# and comment out the following lines
find_package(Drogon CONFIG REQUIRED)
find_package(OpenCV CONFIG REQUIRED) # 추가
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon ${OpenCV_LIBS}) # 추가

2-4. drogon Build

  • cmake 이후 make 진행
cd /root/drogon2/drogon/build/drogon_ctl/testAPI/build
cmake .. 
make

3. 실행

3-1. 실행 전 모델 다운로드

cd /root/drogon2/drogon/build/drogon_ctl/testAPI/build

# detection model
wget https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1

./testAPI

3-2. 실행 및 업로드

  • 아래와 같은 Text Image 사용 (sample image)
drogon 업로드

  • Upload를 통해 파일을 서버로 올림 (http://127.0.0.1:10099/doUpload/) (업로드 구현은 이전 페이지 참조)
업로드

  • 업로드 완료 후 list 확인 (http://127.0.0.1:10099/list/)
리스팅

  • Detection API 호출 (http://127.0.0.1:10099/text-detection?filename=image_in_text.PNG)
opencv text detection

  • Detection 완료 후 list 확인 (http://127.0.0.1:10099/list/)
list

  • Detection 된 이미지 다운로드 후 확인 (http://127.0.0.1:10099/download/image_in_text_detection.jpg)
다운로드

Related Links



댓글

이 블로그의 인기 게시물

윤석열 계엄령 선포! 방산주 대폭발? 관련주 투자 전략 완벽 분석

한국 핵무장 논의와 방위산업 관련주: 핵무기 개발 과정과 유망 종목 분석

[로스트아크] 제작 효율 최적화 위한 영지 세팅