1. 타겟 인식
1.1 전통적인 표적 인식 알고리즘
전통적인 객체 감지는 일반적으로 분류 작업(객체가 속한 범주 결정)과 위치 파악(일반적으로 경계 상자로 표시되는 객체의 위치 결정) 작업을 결합합니다. 이를 위해서는 훈련 데이터 세트에 각 객체의 카테고리 라벨과 해당 bbox 위치 정보가 포함되어 있어야 합니다. 이러한 요구는 가능한 한 많은 카테고리를 탐지하기 위해 얼마나 많은 데이터 카테고리를 라벨링해야 하는지로 이어진다.데이터 라벨링의 경우 인식 대상을 라벨링하기 위해 많은 인건비를 투자해야 하는 경우가 많다.
아래 데이터와 마찬가지로 모든 대상에 수동으로 레이블을 지정하는 것은 여전히 약간 복잡합니다.
1.2 Detic 표적 인식
현 단계의 표적 검출기의 성능 병목 현상과 관련하여 Detic의 저자는 다음과 같이 생각합니다. 현 단계의 표적 검출기의
성능 병목 현상 : 표적 검출기의 추가 성능 향상은 데이터 볼륨의 크기에 의해 제한됩니다. . 기존 객체 감지 작업에서는 우수한 성능을 얻으려면 대규모 교육 데이터가 필요하지만 이 데이터를 얻는 데는 비용과 시간이 많이 소요되는 경우가 많습니다. 이로 인해 성능 병목 현상이 발생합니다.
이미지 분류 데이터의 장점 : 객체 감지와 달리 이미지 분류 작업은 일반적으로 데이터 양이 더 많고 얻기 쉽습니다. 이 데이터에는 더 넓은 범위의 이미지 카테고리가 포함되어 있어 더 큰 어휘에 대한 추론이 가능합니다.
Detic이 제안하는 솔루션 : 표적 탐지 성능 향상의 데이터 한계를 극복하기 위해 저자는 새로운 표적 탐지 훈련 방법인 Detic을 제안했다. Detic의 핵심 아이디어는 이미지 분류 데이터 세트를 사용하여 물체 감지기의 분류 헤드를 훈련시키는 것입니다.
Detic의 주요 기능 :
- 단순성: Detic의 가장 큰 특징은 단순함입니다. 이전의 유사한 약한 감독 방법과 비교할 때 Detic은 구현이 더 간단합니다.
- 폭넓은 적용 가능성: 이전 객체 감지 방법은 일반적으로 복잡한 예측 및 박스 클래스 할당 프로세스가 필요하며 특정 감지기 구조에 대해서만 훈련할 수 있습니다. Detic은 구현하기 쉽고 대부분의 탐지 모델 아키텍처 및 백본에서 사용할 수 있습니다.
1.3 Detic 알고리즘 소개
Detic 논문: https://arxiv.org/abs/2201.02605v3
프로젝트 소스 코드: https://github.com/facebookresearch/Detic
Detic 논문에서 Detic은 분류 및 위치 파악 작업을 두 개의 독립적인 문제로 분리하는 새로운 대상 인식 방법을 언급했습니다. Detic의 데이터 세트는 두 가지 카테고리로 나누어집니다: 하나는 카테고리 라벨과 bbox 정보를 포함하는 전통적인 타겟 감지 데이터 세트이고, 다른 하나는 이미지 분류 데이터와 유사하며 bbox 정보를 포함하지 않는 label-image 데이터입니다. 첫 번째 유형의 데이터에 대해 Detic은 전통적인 목표 감지 방법에 따라 훈련될 수 있으며 분류기의 가중치 W와 bbox 예측 분기 B를 학습할 수 있습니다. 두 번째 유형의 데이터에 대해서는 분류 작업만 수행되며 동일한 분류기 가중치 W를 계속 사용할 수 있습니다. 이 접근 방식의 이점은 두 번째 종류의 데이터를 사용하여 더 다양한 분류기를 훈련할 수 있으므로 데이터 주석 비용을 줄일 수 있다는 것입니다.
현재 상황에서는 객체 검출을 위한 주석 데이터의 양이 이미지 분류에 비해 상당히 제한되어 있습니다. LVIS(Large Vocabulary Instance Segmentation) 데이터 세트를 예로 들면, 1,000개 이상의 카테고리를 포함하는 120,000개의 이미지가 포함되어 있고 OpenImages 데이터 세트에는 500개 이상의 카테고리를 포함하는 180만 개의 이미지가 포함되어 있습니다. 이에 비해 초기 이미지 분류 데이터세트인 ImageNet에는 10년 전에는 21,000개 이상의 카테고리와 1,400만 개 이상의 이미지가 포함되어 있었습니다.
객체 감지 데이터세트에서 사용할 수 있는 카테고리와 샘플의 수가 상대적으로 적기 때문에 제한된 수의 카테고리에 대해 학습된 객체 감지기는 오류가 발생하기 쉽습니다. 그러나 Detic은 이미지 분류 데이터 세트를 사용하므로 더 다양한 카테고리를 감지하고 더 정확한 결과를 제공할 수 있습니다.
Detic 방법의 혁신은 이미지 분류 데이터의 풍부함을 활용하여 더 많은 어휘로 추론함으로써 다양한 범주에 대한 객체 감지기의 민감도와 정확도를 높이는 것입니다. 이는 Detic이 제한된 카테고리 세트에 국한되지 않고 보다 다양하고 정확한 범위의 대상 카테고리를 감지할 수 있음을 의미합니다.
일반적으로 Detic의 이미지 분류 데이터 세트 사용 방법은 표적 탐지에 더 많은 다양성과 정확성을 제공하고, 데이터 제한으로 인한 문제를 극복하고, 데이터 수집 비용을 절감하며, 더 다양한 분류기를 학습시켜 탐지 성능을 향상시킵니다. 성능과 견고성. 이 방법은 객체 탐지에서 데이터 부족 문제를 해결하는 데 매우 중요합니다.
1.4 Detic에서 언급된 문제점과 해결책
문제 및 대안 식별:
저자는 특히 많은 수의 bbox 주석이 필요한 경우 객체 감지에서 약한 지도 학습이 직면한 문제를 먼저 식별합니다. 이러한 문제에는 데이터 주석의 지루함과 리소스 소비, 이미지 수준 감독 신호 사용 시 성능 저하 등이 포함됩니다. 이러한 문제를 해결하기 위해 Detic은 이미지 수준 감독 신호를 효과적으로 활용할 수 있는 새로운 손실 기능을 도입하는 것이 핵심 혁신으로 더욱 간단하고 사용하기 쉬운 대안을 도입했습니다.
새로운 손실 함수:
이미지 수준의 감독 신호를 사용하기 위해 저자는 완전히 새로운 손실 함수를 제안합니다. 이 손실 함수를 사용하면 객체 감지기가 자세한 bbox 주석 없이도 이미지 수준 레이블에서 객체에 대한 정보를 학습할 수 있습니다. 실험에 따르면 이 새로운 손실 함수는 특히 새로운 범주를 감지할 때 매우 효과적이며 객체 감지기의 성능을 크게 향상시킵니다. 이는 모델이 이전에 본 적이 없는 객체 클래스를 더 잘 처리할 수 있음을 의미합니다.
미세 조정이 필요 없는 다양성:
Detic 방법의 또 다른 주요 장점은 훈련된 객체 감지기를 미세 조정 없이 새로운 데이터 세트 및 감지 어휘로 직접 전송할 수 있다는 것입니다.
이 기능은 모델의 다양성과 사용 편의성을 향상시키고 추가 작업과 복잡성을 줄이며 모델의 적용 가치를 더욱 높여줍니다.
1.5 Detic의 손실함수
논문에서 저자는 기존의 약하게 감독된 표적 탐지 방법과 Detic의 차이점을 나열합니다
.
- 전통적인 표적 탐지 방법은 일반적으로 예측 기반 라벨 박스 할당 메커니즘을 채택합니다.
- 이 방법은 먼저 제안 세트를 선택한 다음 각 이미지 수준 카테고리 라벨(예: 사람, 농구 등)을 이러한 제안에 할당합니다.
- 지역 수준의 감독 신호가 부족하기 때문에 이 할당 방법은 특히 객체가 겹치거나 장면이 복잡한 경우 오류가 발생하기 쉽습니다.
데틱 방법:
- Detic은 완전히 다른 접근 방식을 취하여 전체 이미지(보통 거의 전체 사진)를 포괄하는 가장 큰 영역 제안을 선택합니다.
- 그런 다음 Detic은 전체 이미지의 클래스 라벨을 이 가장 큰 영역 제안에 할당합니다.
- 이 접근 방식의 핵심은 Detic이 더 이상 전통적인 제안 수준 라벨 할당에 의존하지 않고 대신 전체 이미지를 전체적으로 처리하고 카테고리 라벨을 할당한다는 것입니다.
- 이 접근 방식은 기존 방법에서 오류를 유발할 수 있는 레이블 및 상자 할당 프로세스를 제거하고 훈련 프로세스를 단순화하며 특히 새로운 범주를 감지할 때 성능을 향상시킵니다.
Detic 방식은 전체 이미지 중 가장 큰 제안을 선택하고 여기에 전체 이미지의 클래스 라벨을 할당함으로써 기존 방식에서 발생할 수 있는 라벨 및 bbox 할당 오류를 제거합니다. 이 단순화되고 혁신적인 접근 방식은 특히 까다로운 시나리오에서 객체 감지의 성능과 견고성을 향상시킬 것으로 예상됩니다.
저자는 타겟 검출기가 이미지 수준 레이블을 사용하여 훈련될 수 있도록 다음과 같은 손실 함수를 제안했습니다.
여기서 f는 제안에 해당하는 RoI 기능을 나타내고, c는 가장 큰 제안에 해당하는 카테고리, 즉 이미지이고 W는 장치의 분류입니다. 동시에 기존 표적 탐지기에서 사용되는 손실과 결합하여 Detic의 최종 손실이 형성됩니다.
2. 모델 변환
정식 출시된 모델은 공식 홈페이지에서 다운로드 받을 수 있으나, 공식 모델은 토치 모델이므로 별도로 배포할 경우 onnx 모델로 변환해야 합니다.
모델 변환 코드:
import argparse
import os
from typing import Dict, List, Tuple
import torch
from torch import Tensor, nn
import detectron2.data.transforms as T
from detectron2.checkpoint import DetectionCheckpointer
from detectron2.config import get_cfg
from detectron2.data import build_detection_test_loader, detection_utils
from detectron2.evaluation import COCOEvaluator, inference_on_dataset, print_csv_format
from detectron2.export import (
STABLE_ONNX_OPSET_VERSION,
TracingAdapter,
dump_torchscript_IR,
scripting_with_instances,
)
from detectron2.modeling import GeneralizedRCNN, RetinaNet, build_model
from detectron2.modeling.postprocessing import detector_postprocess
from detectron2.projects.point_rend import add_pointrend_config
from detectron2.structures import Boxes
from detectron2.utils.env import TORCH_VERSION
from detectron2.utils.file_io import PathManager
from detectron2.utils.logger import setup_logger
def setup_cfg(args):
cfg = get_cfg()
# cuda context is initialized before creating dataloader, so we don't fork anymore
cfg.DATALOADER.NUM_WORKERS = 0
add_pointrend_config(cfg)
cfg.merge_from_file(args.config_file)
cfg.merge_from_list(args.opts)
cfg.freeze()
return cfg
def export_caffe2_tracing(cfg, torch_model, inputs):
from detectron2.export import Caffe2Tracer
tracer = Caffe2Tracer(cfg, torch_model, inputs)
if args.format == "caffe2":
caffe2_model = tracer.export_caffe2()
caffe2_model.save_protobuf(args.output)
# draw the caffe2 graph
caffe2_model.save_graph(os.path.join(args.output, "model.svg"), inputs=inputs)
return caffe2_model
elif args.format == "onnx":
import onnx
onnx_model = tracer.export_onnx()
onnx.save(onnx_model, os.path.join(args.output, "model.onnx"))
elif args.format == "torchscript":
ts_model = tracer.export_torchscript()
with PathManager.open(os.path.join(args.output, "model.ts"), "wb") as f:
torch.jit.save(ts_model, f)
dump_torchscript_IR(ts_model, args.output)
# experimental. API not yet final
def export_scripting(torch_model):
assert TORCH_VERSION >= (1, 8)
fields = {
"proposal_boxes": Boxes,
"objectness_logits": Tensor,
"pred_boxes": Boxes,
"scores": Tensor,
"pred_classes": Tensor,
"pred_masks": Tensor,
"pred_keypoints": torch.Tensor,
"pred_keypoint_heatmaps": torch.Tensor,
}
assert args.format == "torchscript", "Scripting only supports torchscript format."
class ScriptableAdapterBase(nn.Module):
# Use this adapter to workaround https://github.com/pytorch/pytorch/issues/46944
# by not retuning instances but dicts. Otherwise the exported model is not deployable
def __init__(self):
super().__init__()
self.model = torch_model
self.eval()
if isinstance(torch_model, GeneralizedRCNN):
class ScriptableAdapter(ScriptableAdapterBase):
def forward(self, inputs: Tuple[Dict[str, torch.Tensor]]) -> List[Dict[str, Tensor]]:
instances = self.model.inference(inputs, do_postprocess=False)
return [i.get_fields() for i in instances]
else:
class ScriptableAdapter(ScriptableAdapterBase):
def forward(self, inputs: Tuple[Dict[str, torch.Tensor]]) -> List[Dict[str, Tensor]]:
instances = self.model(inputs)
return [i.get_fields() for i in instances]
ts_model = scripting_with_instances(ScriptableAdapter(), fields)
with PathManager.open(os.path.join(args.output, "model.ts"), "wb") as f:
torch.jit.save(ts_model, f)
dump_torchscript_IR(ts_model, args.output)
# TODO inference in Python now missing postprocessing glue code
return None
# experimental. API not yet final
def export_tracing(torch_model, inputs):
assert TORCH_VERSION >= (1, 8)
image = inputs[0]["image"]
inputs = [{
"image": image}] # remove other unused keys
if isinstance(torch_model, GeneralizedRCNN):
def inference(model, inputs):
# use do_postprocess=False so it returns ROI mask
inst = model.inference(inputs, do_postprocess=False)[0]
return [{
"instances": inst}]
else:
inference = None # assume that we just call the model directly
traceable_model = TracingAdapter(torch_model, inputs, inference)
if args.format == "torchscript":
ts_model = torch.jit.trace(traceable_model, (image,))
with PathManager.open(os.path.join(args.output, "model.ts"), "wb") as f:
torch.jit.save(ts_model, f)
dump_torchscript_IR(ts_model, args.output)
elif args.format == "onnx":
with PathManager.open(os.path.join(args.output, "model.onnx"), "wb") as f:
torch.onnx.export(traceable_model, (image,), f, opset_version=STABLE_ONNX_OPSET_VERSION)
logger.info("Inputs schema: " + str(traceable_model.inputs_schema))
logger.info("Outputs schema: " + str(traceable_model.outputs_schema))
if args.format != "torchscript":
return None
if not isinstance(torch_model, (GeneralizedRCNN, RetinaNet)):
return None
def eval_wrapper(inputs):
"""
The exported model does not contain the final resize step, which is typically
unused in deployment but needed for evaluation. We add it manually here.
"""
input = inputs[0]
instances = traceable_model.outputs_schema(ts_model(input["image"]))[0]["instances"]
postprocessed = detector_postprocess(instances, input["height"], input["width"])
return [{
"instances": postprocessed}]
return eval_wrapper
def get_sample_inputs(args):
if args.sample_image is None:
# get a first batch from dataset
data_loader = build_detection_test_loader(cfg, cfg.DATASETS.TEST[0])
first_batch = next(iter(data_loader))
return first_batch
else:
# get a sample data
original_image = detection_utils.read_image(args.sample_image, format=cfg.INPUT.FORMAT)
# Do same preprocessing as DefaultPredictor
aug = T.ResizeShortestEdge(
[cfg.INPUT.MIN_SIZE_TEST, cfg.INPUT.MIN_SIZE_TEST], cfg.INPUT.MAX_SIZE_TEST
)
height, width = original_image.shape[:2]
image = aug.get_transform(original_image).apply_image(original_image)
image = torch.as_tensor(image.astype("float32").transpose(2, 0, 1))
inputs = {
"image": image, "height": height, "width": width}
# Sample ready
sample_inputs = [inputs]
return sample_inputs
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Export a model for deployment.")
parser.add_argument(
"--format",
choices=["caffe2", "onnx", "torchscript"],
help="output format",
default="torchscript",
)
parser.add_argument(
"--export-method",
choices=["caffe2_tracing", "tracing", "scripting"],
help="Method to export models",
default="tracing",
)
parser.add_argument("--config-file", default="", metavar="FILE", help="path to config file")
parser.add_argument("--sample-image", default=None, type=str, help="sample image for input")
parser.add_argument("--run-eval", action="store_true")
parser.add_argument("--output", help="output directory for the converted model")
parser.add_argument(
"opts",
help="Modify config options using the command-line",
default=None,
nargs=argparse.REMAINDER,
)
args = parser.parse_args()
logger = setup_logger()
logger.info("Command line arguments: " + str(args))
PathManager.mkdirs(args.output)
# Disable re-specialization on new shapes. Otherwise --run-eval will be slow
torch._C._jit_set_bailout_depth(1)
cfg = setup_cfg(args)
# create a torch model
torch_model = build_model(cfg)
DetectionCheckpointer(torch_model).resume_or_load(cfg.MODEL.WEIGHTS)
torch_model.eval()
# convert and save model
if args.export_method == "caffe2_tracing":
sample_inputs = get_sample_inputs(args)
exported_model = export_caffe2_tracing(cfg, torch_model, sample_inputs)
elif args.export_method == "scripting":
exported_model = export_scripting(torch_model)
elif args.export_method == "tracing":
sample_inputs = get_sample_inputs(args)
exported_model = export_tracing(torch_model, sample_inputs)
# run evaluation with the converted model
if args.run_eval:
assert exported_model is not None, (
"Python inference is not yet implemented for "
f"export_method={
args.export_method}, format={
args.format}."
)
logger.info("Running evaluation ... this takes a long time if you export to CPU.")
dataset = cfg.DATASETS.TEST[0]
data_loader = build_detection_test_loader(cfg, dataset)
# NOTE: hard-coded evaluator. change to the evaluator for your dataset
evaluator = COCOEvaluator(dataset, output_dir=args.output)
metrics = inference_on_dataset(exported_model, data_loader, evaluator)
print_csv_format(metrics)
logger.info("Success.")
3. 모델 C++ 배포
모델 C++는 ONNXRuntime 또는 OpenCV의 DNN을 사용하여 배포할 수 있습니다. ONNXRuntime은 다양한 하드웨어 플랫폼에서 딥 러닝 모델을 실행하는 데 사용되는 오픈 소스 고성능 추론 엔진입니다. Microsoft에서 개발했으며 ONNX(Open Neural Network Exchange)의 일부로 다양한 딥 러닝 프레임워크(예: PyTorch, TensorFlow, ONNX 등)와 호환됩니다. ONNX 런타임의 주요 기능은 다음과 같습니다.
교차 플랫폼 지원 : ONNX Runtime은 CPU, GPU, 에지 장치를 포함한 여러 하드웨어 플랫폼을 지원하여 다양한 하드웨어에서 딥 러닝 모델을 실행할 수 있습니다.
고성능 : ONNX Runtime은 대규모 모델에 대한 효율적인 지원을 포함하여 실시간 애플리케이션의 추론을 위한 고성능에 최적화되어 있습니다.
오픈 소스 : ONNX Runtime은 오픈 소스 프로젝트이며 필요에 따라 사용자 정의하고 확장할 수 있습니다.
프레임워크 간 호환성 : ONNX Runtime은 여러 딥 러닝 프레임워크에서 생성된 모델을 지원하여 다양한 프레임워크 간의 모델 변환 및 추론을 가능하게 합니다.
경량 : ONNX 런타임은 메모리 및 컴퓨팅 리소스 요구 사항이 상대적으로 적기 때문에 임베디드 및 에지 장치에서 실행될 수 있습니다.
ONNX 표준 지원 : ONNX 런타임은 모델 상호 운용성과 이식성을 달성하는 데 도움이 되는 개방형 모델 표현 표준인 ONNX 표준을 따릅니다.
#include "RAMDetic.h"
RAMDetic::RAMDetic()
{
}
bool file_exists(std::string& name)
{
std::ifstream f(name.c_str());
return f.good();
}
int RAMDetic::init_model(std::string model_path, std::string names_path)
{
if (!(file_exists(model_path) && file_exists(names_path)))
{
std::cout << "model or class name file does not exist !" << std::endl;
return -1;
}
OrtStatus* status = OrtSessionOptionsAppendExecutionProvider_CUDA(sessionOptions, 0);//GPU加速度,如果没有安装CUDA,要注掉
sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
std::wstring widestr = std::wstring(model_path.begin(), model_path.end());
ort_session = new Ort::Session(env, widestr.c_str(), sessionOptions);
///ort_session = new Session(env, model_path.c_str(), sessionOptions); ///linux写法
size_t numInputNodes = ort_session->GetInputCount();
size_t numOutputNodes = ort_session->GetOutputCount();
Ort::AllocatorWithDefaultOptions allocator;
for (int i = 0; i < numInputNodes; i++)
{
input_names.push_back(ort_session->GetInputName(i, allocator));
//AllocatedStringPtr input_name_Ptr = ort_session->GetInputNameAllocated(i, allocator);
//input_names.push_back(input_name_Ptr.get());
Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);
auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();
auto input_dims = input_tensor_info.GetShape();
input_node_dims.push_back(input_dims);
}
for (int i = 0; i < numOutputNodes; i++)
{
output_names.push_back(ort_session->GetOutputName(i, allocator));
//AllocatedStringPtr output_name_Ptr = ort_session->GetInputNameAllocated(i, allocator);
//output_names.push_back(output_name_Ptr.get());
Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();
auto output_dims = output_tensor_info.GetShape();
output_node_dims.push_back(output_dims);
}
std::ifstream ifs(names_path);
std::string line;
while (getline(ifs, line))
{
this->class_names.push_back(line);
}
return 0;
}
void RAMDetic::preprocess(cv::Mat &srcimg)
{
cv::Mat dstimg;
cvtColor(srcimg, dstimg, cv::COLOR_BGR2RGB);
int im_h = srcimg.rows;
int im_w = srcimg.cols;
float oh, ow, scale;
if (im_h < im_w)
{
scale = (float)max_size / (float)im_h;
oh = max_size;
ow = scale * (float)im_w;
}
else
{
scale = (float)max_size / (float)im_h;
oh = scale * (float)im_h;
ow = max_size;
}
float max_hw = std::max(oh, ow);
if (max_hw > max_size)
{
scale = (float)max_size / max_hw;
oh *= scale;
ow *= scale;
}
resize(dstimg, dstimg, cv::Size(int(ow + 0.5), int(oh + 0.5)), cv::INTER_LINEAR);
this->inpHeight = dstimg.rows;
this->inpWidth = dstimg.cols;
this->input_image_.resize(this->inpWidth * this->inpHeight * dstimg.channels());
int k = 0;
for (int c = 0; c < 3; c++)
{
for (int i = 0; i < this->inpHeight; i++)
{
for (int j = 0; j < this->inpWidth; j++)
{
float pix = dstimg.ptr<uchar>(i)[j * 3 + c];
this->input_image_[k] = pix;
k++;
}
}
}
}
std::vector<BoxInfo> RAMDetic::detect(cv::Mat &srcimg, int _max_size)
{
max_size = _max_size;
int im_h = srcimg.rows;
int im_w = srcimg.cols;
this->preprocess(srcimg);
std::array<int64_t, 4> input_shape_{
1, 3, this->inpHeight, this->inpWidth };
auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
Ort::Value input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info,
input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());
// 开始推理
std::vector<Ort::Value> ort_outputs = ort_session->Run(Ort::RunOptions{
nullptr },
&input_names[0], &input_tensor_, 1, output_names.data(), output_names.size());
const float* pred_boxes = ort_outputs[0].GetTensorMutableData<float>();
const float* scores = ort_outputs[1].GetTensorMutableData<float>();
const int* pred_classes = ort_outputs[2].GetTensorMutableData<int>();
//const float *pred_masks = ort_outputs[3].GetTensorMutableData<float>();
int num_box = ort_outputs[0].GetTensorTypeAndShapeInfo().GetShape()[0];
const float scale_x = float(im_w) / float(inpWidth);
const float scale_y = float(im_h) / float(inpHeight);
std::vector<BoxInfo> preds;
for (int i = 0; i < num_box; i++)
{
float xmin = pred_boxes[i * 4] * scale_x;
float ymin = pred_boxes[i * 4 + 1] * scale_y;
float xmax = pred_boxes[i * 4 + 2] * scale_x;
float ymax = pred_boxes[i * 4 + 3] * scale_y;
xmin = std::min(std::max(xmin, 0.f), float(im_w));
ymin = std::min(std::max(ymin, 0.f), float(im_h));
xmax = std::min(std::max(xmax, 0.f), float(im_w));
ymax = std::min(std::max(ymax, 0.f), float(im_h));
const float threshold = 0;
const float width = xmax - xmin;
const float height = ymax - ymin;
if (width > threshold && height > threshold)
{
preds.push_back({
int(xmin), int(ymin), int(xmax), int(ymax), scores[i], class_names[pred_classes[i]] });
}
}
return preds;
}
void RAMDetic::draw(cv::Mat& cv_src, cv::Mat& cv_dst)
{
if (cv_src.empty())
{
return;
}
cv_dst = cv_src.clone();
std::vector<BoxInfo> preds = detect(cv_dst);
cv::RNG rng(12345);//产生随机数
for (size_t i = 0; i < preds.size(); ++i)
{
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
cv::rectangle(cv_dst, cv::Point(preds[i].xmin, preds[i].ymin),
cv::Point(preds[i].xmax, preds[i].ymax), cv::Scalar(b, g, r), 2);
std::string label = cv::format("%.2f", preds[i].score);
cv::putText(cv_dst, label, cv::Point(preds[i].xmin,
preds[i].ymin - 5), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(b, g, r), 1);
}
}
void RAMDetic::detect_video(std::string input_path, std::string output_path)
{
cv::VideoCapture video_capture(input_path);
if (!video_capture.isOpened())
{
std::cout << "Can not open video: " << input_path << "\n";
return;
}
cv::Size S = cv::Size((int)video_capture.get(cv::CAP_PROP_FRAME_WIDTH),
(int)video_capture.get(cv::CAP_PROP_FRAME_HEIGHT));
cv::VideoWriter output_video(output_path, cv::VideoWriter::fourcc('m', 'p', '4', 'v'),25.0, S);
if (!output_video.isOpened())
{
std::cout << "Can not open writer: " << output_path << "\n";
return;
}
cv::Mat cv_mat;
while (video_capture.read(cv_mat))
{
cv::Mat bg_upsample;
draw(cv_mat, bg_upsample);
output_video << bg_upsample;
}
video_capture.release();
output_video.release();
}
시험 결과:
오픈 월드 표적 탐지
이미지 감지 결과:
4. Python 코드 배포
Python 코드 추론을 위해서는 opencvt 및 onnxruntime 두 라이브러리를 설치해야 합니다.
import argparse
import cv2
import numpy as np
import onnxruntime as ort
class Detic():
def __init__(self, modelpath, detection_width=800, confThreshold=0.8):
# net = cv2.dnn.readNet(modelpath)
so = ort.SessionOptions()
so.log_severity_level = 3
self.session = ort.InferenceSession(modelpath, so)
model_inputs = self.session.get_inputs()
self.input_name = model_inputs[0].name
self.max_size = detection_width
self.confThreshold = confThreshold
self.class_names = list(map(lambda x: x.strip(), open('models/class_names.txt').readlines()))
self.assigned_colors = np.random.randint(0,high=256, size=(len(self.class_names), 3)).tolist()
def preprocess(self, srcimg):
im_h, im_w, _ = srcimg.shape
dstimg = cv2.cvtColor(srcimg, cv2.COLOR_BGR2RGB)
if im_h < im_w:
scale = self.max_size / im_h
oh, ow = self.max_size, scale * im_w
else:
scale = self.max_size / im_w
oh, ow = scale * im_h, self.max_size
max_hw = max(oh, ow)
if max_hw > self.max_size:
scale = self.max_size / max_hw
oh *= scale
ow *= scale
ow = int(ow + 0.5)
oh = int(oh + 0.5)
dstimg = cv2.resize(dstimg, (ow, oh))
return dstimg
def post_processing(self, pred_boxes, scores, pred_classes, pred_masks, im_hw, pred_hw):
scale_x, scale_y = (im_hw[1] / pred_hw[1], im_hw[0] / pred_hw[0])
pred_boxes[:, 0::2] *= scale_x
pred_boxes[:, 1::2] *= scale_y
pred_boxes[:, [0, 2]] = np.clip(pred_boxes[:, [0, 2]], 0, im_hw[1])
pred_boxes[:, [1, 3]] = np.clip(pred_boxes[:, [1, 3]], 0, im_hw[0])
threshold = 0
widths = pred_boxes[:, 2] - pred_boxes[:, 0]
heights = pred_boxes[:, 3] - pred_boxes[:, 1]
keep = (widths > threshold) & (heights > threshold)
pred_boxes = pred_boxes[keep]
scores = scores[keep]
pred_classes = pred_classes[keep]
pred_masks = pred_masks[keep]
# mask_threshold = 0.5
# pred_masks = paste_masks_in_image(
# pred_masks[:, 0, :, :], pred_boxes,
# (im_hw[0], im_hw[1]), mask_threshold
# )
pred = {
'pred_boxes': pred_boxes,
'scores': scores,
'pred_classes': pred_classes,
'pred_masks': pred_masks,
}
return pred
def draw_predictions(self, img, predictions):
height, width = img.shape[:2]
default_font_size = int(max(np.sqrt(height * width) // 90, 10))
boxes = predictions["pred_boxes"].astype(np.int64)
scores = predictions["scores"]
classes_id = predictions["pred_classes"].tolist()
# masks = predictions["pred_masks"].astype(np.uint8)
num_instances = len(boxes)
print('detect', num_instances, 'instances')
for i in range(num_instances):
x0, y0, x1, y1 = boxes[i]
color = self.assigned_colors[classes_id[i]]
cv2.rectangle(img, (x0, y0), (x1, y1), color=color,thickness=default_font_size // 4)
text = "{} {:.0f}%".format(self.class_names[classes_id[i]], round(scores[i],2) * 100)
cv2.putText(img, text, (x0, y0 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness=1, lineType=cv2.LINE_AA)
return img
def detect(self, srcimg):
im_h, im_w = srcimg.shape[:2]
dstimg = self.preprocess(srcimg)
pred_hw = dstimg.shape[:2]
input_image = np.expand_dims(dstimg.transpose(2, 0, 1), axis=0).astype(np.float32)
# Inference
pred_boxes, scores, pred_classes, pred_masks = self.session.run(None, {
self.input_name: input_image})
preds = self.post_processing(pred_boxes, scores, pred_classes, pred_masks, (im_h, im_w), pred_hw)
return preds
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--imgpath", type=str, default='desk.jpg', help="image path")
parser.add_argument("--confThreshold", default=0.5, type=float, help='class confidence')
parser.add_argument("--modelpath", type=str, default='models/Detic_896.onnx', help="onnxmodel path")
args = parser.parse_args()
mynet = Detic(args.modelpath, confThreshold=args.confThreshold)
srcimg = cv2.imread(args.imgpath)
preds = mynet.detect(srcimg)
srcimg = mynet.draw_predictions(srcimg, preds)
# cv2.imwrite('result.jpg', srcimg)
winName = 'Deep learning Detic in ONNXRuntime'
cv2.namedWindow(winName, cv2.WINDOW_NORMAL)
cv2.imshow(winName, srcimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
5. 모델 및 소스 코드
1. 모델과 소스 코드가 csdn에 업로드되었습니다: https://download.csdn.net/download/matt45m/88335108
2. 이 프로젝트에 관심이 있거나 설치 과정에서 오류가 발생하면 내 내용을 추가할 수 있습니다. 펭귄 그룹: 487350510, 같이 토론해 볼까요.