이미 딥러닝 학습이 완료된 모델을 OpenCV
의 DNN
모듈에서 로드하여 사용하는 방법을 알아보겠다.
사용하는 딥러닝 모델이 OpenCV DNN
모듈이 지원하는 특정 형식의 모델 파일이어야 한다.
지원하는 모델은 아래와 같다.
1. Caffe 모델
2. TensorFlow 모델
3. PyTorch 모델
4. ONNX (Open Neural Network Exchange) 모델
5. Darknet 모델
사진을 통해 강아지 여부를 판단하기 위해서 bvlc_googlenet
모델을 사용하기로 하였다.
blvc_googlenet
https://github.com/BVLC/caffe/tree/master/models/bvlc_googlenet
blvc_googlenet class list txt
https://gist.github.com/yrevar/942d3a0ac09ec9e5eb3a#file-imagenet1000_clsidx_to_labels-txt
위 링크를 통해서 3개의 파일을 다운로드 해야한다.
find_package(OpenCV REQUIRED COMPONENTS core highgui imgcodecs imgproc dnn) # 시스템에 설치된 OpenCV 라이브러리를 찾는다.
find_package
에서 dnn
을 사용하기 위해 추가해준다.
#include <iostream>
#include <opencv4/opencv2/opencv.hpp>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
int main(int, char**){
/* ------ 01 모델 파일, 설정 파일, 클래스 이름 파일 경로 설정 ------ */
std::string modelWeights = "../bvlc_googlenet.caffemodel"; // 모델 가중치 파일
std::string modelConfig = "../deploy.prototxt"; // 모델 구조 파일
std::string classNamesFile = "../synset_words.txt"; // 클래스 이름 파일
/* ------ 02 클래스 이름 로드 ------ */
std::vector<std::string> classNames;
std::ifstream ifs(classNamesFile.c_str());
if (!ifs.is_open()) {
std::cerr << "오류: 클래스 이름 파일(" << classNamesFile << ")을 찾거나 열 수 없습니다." << std::endl;
return -1;
}
std::string line;
while (std::getline(ifs, line)) {
classNames.push_back(line);
}
ifs.close(); // 파일 스트림 닫기
/* ------ 03 딥러닝 모델 로드 ------ */
cv::dnn::Net net;
try {
net = cv::dnn::readNet(modelWeights, modelConfig);
if (net.empty()) {
std::cerr << "오류: 모델 파일(" << modelWeights << ") 또는 설정 파일(" << modelConfig << ")을 로드할 수 없습니다." << std::endl;
return -1;
}
} catch (const cv::Exception& e) {
std::cerr << "오류: 딥러닝 모델 로드 중 예외 발생: " << e.what() << std::endl;
return -1;
}
std::cout << "모델 로드 성공!" << std::endl;
/* ------ 04 추론에 사용할 이미지 불러오기 ------ */
std::string image_path = "dog.jpg";
cv::Mat image = cv::imread(image_path);
if (image.empty()) {
std::cerr << "오류: 이미지 파일(" << image_path << ")을 찾거나 로드할 수 없습니다." << std::endl;
return -1;
}
std::cout << "이미지 로드 성공: " << image_path << std::endl;
/* ------ 05 이미지 전처리 ------ */
int inputWidth = 224;
int inputHeight = 224;
double scale = 1.0; // 픽셀 값 자체를 사용 (평균 빼기로 정규화)
cv::Scalar mean = cv::Scalar(104, 117, 123); // ImageNet 데이터셋의 BGR 채널별 평균
bool swapRB = false; // Caffe 모델은 대부분 BGR 순서를 기대하므로 false
// cv::dnn::blobFromImage: 이미지를 딥러닝 모델의 입력 형식(blob)으로 변환
cv::Mat blob = cv::dnn::blobFromImage(
image, // 입력 이미지
scale, // 스케일 팩터
cv::Size(inputWidth, inputHeight), // 출력 blob의 공간 크기
mean, // 각 채널에서 뺄 평균 값 (픽셀 값에서 이 평균을 뺌)
swapRB, // BGR -> RGB 채널 순서 교환 여부 (true = 교환, false = 유지)
false // 크롭 여부 (true = 중앙 크롭, false = 리사이즈만)
);
std::cout << "이미지 전처리 완료." << std::endl;
/* ------ 06 모델에 입력 설정 및 추론 실행 ------ */
net.setInput(blob);
cv::Mat output = net.forward(); // 추론 실행, 결과는 각 클래스에 대한 확률 배열
/* ------ 07 추론 결과 분석 ------ */
cv::Point classIdPoint; // 최고 확률 클래스의 위치
double confidence; // 최고 확률 값
// output은 1x1000 Mat 형태일 것이므로, minMaxLoc은 전체 Mat에서 최고값을 찾습니다.
cv::minMaxLoc(output.reshape(1, 1), 0, &confidence, 0, &classIdPoint);
int classId = classIdPoint.x; // 가장 높은 확률을 가진 클래스의 인덱스
std::string predictedClassName = "알 수 없음";
if (classId >= 0 && classId < classNames.size()) {
predictedClassName = classNames[classId];
}
std::cout << "------------------------------------------" << std::endl;
std::cout << "예측된 클래스: " << predictedClassName << " (확률: " << confidence * 100 << "%)" << std::endl;
std::cout << "------------------------------------------" << std::endl;
/* ------ 08 강아지인지 판단하는 로직 ------ */
// ImageNet 클래스 목록에서 강아지 관련 클래스 이름들을 찾아 조건으로 설정합니다.
// 여기서는 몇 가지 예시만 포함합니다. synset_words.txt를 확인하여 더 추가할 수 있습니다.
bool isDog = false;
std::string lowerCasePredictedClassName = predictedClassName;
// 예측된 클래스 이름을 소문자로 변환하여 비교 (대소문자 구분 없이)
std::transform(lowerCasePredictedClassName.begin(), lowerCasePredictedClassName.end(), lowerCasePredictedClassName.begin(), ::tolower);
// 실제 synset_words.txt 파일의 강아지 관련 클래스 이름을 정확히 확인하고 추가하세요.
// 예를 들어, "n02084071 labrador retriever"에서 "labrador retriever"만 추출했다면,
// "labrador retriever"나 "dog" 등의 문자열을 포함하는지 확인합니다.
if (lowerCasePredictedClassName.find("dog") != std::string::npos ||
lowerCasePredictedClassName.find("terrier") != std::string::npos || // 특정 강아지 품종군
lowerCasePredictedClassName.find("hound") != std::string::npos || // 특정 강아지 품종군
lowerCasePredictedClassName.find("poodle") != std::string::npos ||
lowerCasePredictedClassName.find("chihuahua") != std::string::npos ||
lowerCasePredictedClassName.find("labrador") != std::string::npos ||
lowerCasePredictedClassName.find("retriever") != std::string::npos ||
lowerCasePredictedClassName.find("shepherd") != std::string::npos ||
lowerCasePredictedClassName.find("bulldog") != std::string::npos ||
lowerCasePredictedClassName.find("welsh") != std::string::npos
) {
isDog = true;
}
// 50% 이상의 확률일 때만 강아지로 판단하는 임계값 설정
if (isDog && confidence * 100 > 50.0) {
std::cout << "판단: 이 사진은 강아지입니다!" << std::endl;
} else {
std::cout << "판단: 이 사진은 강아지가 아니거나, 판단하기 어렵습니다." << std::endl;
}
std::cout << "------------------------------------------" << std::endl;
return 0;
}
귀여운 웰시코기 사진을 로드하여 모델에게 추론을 실행 결과 값은 아래와 같다.
약 81.8196%
의 확률로 예측을 해주었다.
이미 잘 학습되고 만들어진 모델을 다운로드하여 OpenCV dnn
모듈을 활용할 수 있다. 처음 해보는 작업이라 익숙하지는 않지만 자주 사용해봐서 딥러닝 모델과 친해질 수 있도록 하겠다.