[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


Recommended Link


댓글

이 블로그의 인기 게시물

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

[로스트아크] 로스트아크 생활 도구 옵션

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