283 lines
12 KiB
C++
Raw Normal View History

#include "yolo_runtime_backend.h"
#include "yolo_mask_utils.h"
#if defined(YOLO_BACKEND_SEG)
#include "yolov8_seg.h"
#elif defined(YOLO_BACKEND_CLS)
#include "mobilenet.h"
#elif defined(YOLO_BACKEND_POSE)
#include "yolov8-pose.h"
#elif defined(YOLO_BACKEND_OBB)
#include "yolov8-obb.h"
#else
#include "yolov8.h"
#endif
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <new>
#include <vector>
extern "C" void yolo_adapter_set_thresholds(float confidence, float nms);
#if defined(YOLO_BACKEND_SEG)
extern "C" void yolo_adapter_set_class_agnostic_nms(int enabled);
#endif
extern "C" void yolo_adapter_get_performance(
double* preprocess_ms,
double* inference_ms,
double* postprocess_ms);
struct yolo_runtime_backend {
rknn_app_context_t context{};
std::mutex context_mutex;
bool initialized = false;
float confidence_threshold = 0.25f;
float nms_threshold = 0.45f;
bool class_agnostic_nms = false;
std::vector<std::vector<unsigned char>> segmentation_masks;
};
namespace {
int packed_row_bytes(const yolo_image_view_v1_t& image) {
switch (image.pixel_format) {
case YOLO_PIXEL_FORMAT_GRAY8:
case YOLO_PIXEL_FORMAT_NV12:
case YOLO_PIXEL_FORMAT_NV21: return image.width;
case YOLO_PIXEL_FORMAT_RGB888:
case YOLO_PIXEL_FORMAT_BGR888: return image.width * 3;
case YOLO_PIXEL_FORMAT_RGBA8888:
case YOLO_PIXEL_FORMAT_BGRA8888: return image.width * 4;
default: return 0;
}
}
bool prepare_image(const yolo_image_view_v1_t& source, image_buffer_t& destination, std::vector<unsigned char>& owned) {
const int packed_stride = packed_row_bytes(source);
if (packed_stride <= 0) return false;
const int source_stride = source.row_stride_bytes > 0 ? source.row_stride_bytes : packed_stride;
if (source_stride < packed_stride) return false;
int rows = source.height;
if (source.pixel_format == YOLO_PIXEL_FORMAT_NV12 || source.pixel_format == YOLO_PIXEL_FORMAT_NV21) rows = source.height * 3 / 2;
const bool swap_red_blue = source.pixel_format == YOLO_PIXEL_FORMAT_BGR888 || source.pixel_format == YOLO_PIXEL_FORMAT_BGRA8888;
const bool copy_rows = source_stride != packed_stride;
const unsigned char* pixels = source.data;
if (swap_red_blue || copy_rows) {
owned.resize(static_cast<size_t>(packed_stride) * rows);
for (int row = 0; row < rows; ++row) {
const unsigned char* input = source.data + static_cast<size_t>(row) * source_stride;
unsigned char* output = owned.data() + static_cast<size_t>(row) * packed_stride;
std::memcpy(output, input, static_cast<size_t>(packed_stride));
if (swap_red_blue && row < source.height) {
const int channels = source.pixel_format == YOLO_PIXEL_FORMAT_BGR888 ? 3 : 4;
for (int column = 0; column < source.width; ++column) std::swap(output[column * channels], output[column * channels + 2]);
}
}
pixels = owned.data();
}
destination.width = source.width;
destination.height = source.height;
destination.width_stride = source.width;
destination.height_stride = source.height;
destination.virt_addr = const_cast<unsigned char*>(pixels);
destination.size = packed_stride * rows;
destination.fd = 0;
switch (source.pixel_format) {
case YOLO_PIXEL_FORMAT_GRAY8: destination.format = IMAGE_FORMAT_GRAY8; break;
case YOLO_PIXEL_FORMAT_RGB888:
case YOLO_PIXEL_FORMAT_BGR888: destination.format = IMAGE_FORMAT_RGB888; break;
case YOLO_PIXEL_FORMAT_RGBA8888:
case YOLO_PIXEL_FORMAT_BGRA8888: destination.format = IMAGE_FORMAT_RGBA8888; break;
case YOLO_PIXEL_FORMAT_NV12: destination.format = IMAGE_FORMAT_YUV420SP_NV12; break;
case YOLO_PIXEL_FORMAT_NV21: destination.format = IMAGE_FORMAT_YUV420SP_NV21; break;
default: return false;
}
return true;
}
bool valid_image(const yolo_image_view_v1_t* image) {
return image && image->struct_size >= sizeof(yolo_image_view_v1_t) && image->version == YOLO_IMAGE_VIEW_ABI_VERSION && image->data && image->width > 0 && image->height > 0;
}
#if defined(YOLO_BACKEND_OBB)
void set_box(yolo_backend_result_t& target, const image_obb_box_t& box) {
target.bbox.x = static_cast<float>(box.x);
target.bbox.y = static_cast<float>(box.y);
target.bbox.width = static_cast<float>(box.w);
target.bbox.height = static_cast<float>(box.h);
target.bbox.angle = box.angle;
target.bbox.has_angle = 1;
}
#else
void set_box(yolo_backend_result_t& target, const image_rect_t& box) {
target.bbox.x = static_cast<float>(box.left);
target.bbox.y = static_cast<float>(box.top);
target.bbox.width = static_cast<float>(box.right - box.left);
target.bbox.height = static_cast<float>(box.bottom - box.top);
}
#endif
} // namespace
extern "C" int yolo_backend_get_abi_version(void) { return YOLO_RUNTIME_BACKEND_ABI_VERSION; }
extern "C" int yolo_backend_create(
const char* model_path,
const char* config_path,
float confidence_threshold,
float nms_threshold,
int class_agnostic_nms,
yolo_runtime_backend_t** output) {
if (!model_path || !output) return YOLO_STATUS_INVALID_ARGUMENT;
(void)config_path;
auto* backend = new (std::nothrow) yolo_runtime_backend();
if (!backend) return YOLO_STATUS_INITIALIZATION_FAILED;
backend->confidence_threshold = confidence_threshold;
backend->nms_threshold = nms_threshold;
backend->class_agnostic_nms = class_agnostic_nms != 0;
#if defined(YOLO_BACKEND_SEG)
const int status = init_yolov8_seg_model(model_path, &backend->context);
#elif defined(YOLO_BACKEND_CLS)
const int status = init_mobilenet_model(model_path, &backend->context);
#elif defined(YOLO_BACKEND_POSE)
const int status = init_yolov8_pose_model(model_path, &backend->context);
#elif defined(YOLO_BACKEND_OBB)
const int status = init_yolov8_obb_model(model_path, &backend->context);
#else
const int status = init_yolov8_model(model_path, &backend->context);
#endif
if (status != 0) { delete backend; return YOLO_STATUS_INITIALIZATION_FAILED; }
backend->initialized = true;
*output = backend;
return YOLO_STATUS_OK;
}
extern "C" int yolo_backend_infer(yolo_runtime_backend_t* backend, const yolo_image_view_v1_t* image, yolo_backend_result_list_t* results) {
if (!backend || !backend->initialized || !valid_image(image) || !results) return YOLO_STATUS_INVALID_ARGUMENT;
std::lock_guard<std::mutex> lock(backend->context_mutex);
std::memset(results, 0, sizeof(*results));
image_buffer_t input{};
std::vector<unsigned char> owned_pixels;
if (!prepare_image(*image, input, owned_pixels)) return YOLO_STATUS_INVALID_ARGUMENT;
const auto start = std::chrono::steady_clock::now();
#if defined(YOLO_BACKEND_CLS)
mobilenet_result classifications[5]{};
const int status = inference_mobilenet_model(&backend->context, &input, classifications, 5);
results->task = YOLO_TASK_CLS;
if (status == 0) {
for (const auto& classification : classifications) {
if (classification.cls < 0 || results->count >= YOLO_BACKEND_MAX_RESULTS) continue;
auto& result = results->results[results->count++];
result.task = YOLO_TASK_CLS;
result.class_id = classification.cls;
result.score = classification.score;
std::snprintf(result.label, sizeof(result.label), "class_%d", result.class_id);
}
}
#else
object_detect_result_list detections{};
#if defined(YOLO_BACKEND_SEG)
yolo_adapter_set_thresholds(backend->confidence_threshold, backend->nms_threshold);
yolo_adapter_set_class_agnostic_nms(backend->class_agnostic_nms ? 1 : 0);
const int status = inference_yolov8_seg_model(&backend->context, &input, &detections);
results->task = YOLO_TASK_SEG;
#elif defined(YOLO_BACKEND_POSE)
yolo_adapter_set_thresholds(backend->confidence_threshold, backend->nms_threshold);
const int status = inference_yolov8_pose_model(&backend->context, &input, &detections);
results->task = YOLO_TASK_POSE;
#elif defined(YOLO_BACKEND_OBB)
yolo_adapter_set_thresholds(backend->confidence_threshold, backend->nms_threshold);
const int status = inference_yolov8_obb_model(&backend->context, &input, &detections);
results->task = YOLO_TASK_OBB;
#else
yolo_adapter_set_thresholds(backend->confidence_threshold, backend->nms_threshold);
const int status = inference_yolov8_model(&backend->context, &input, &detections);
results->task = YOLO_TASK_DETECT;
#endif
if (status == 0) {
#if defined(YOLO_BACKEND_SEG)
backend->segmentation_masks.clear();
backend->segmentation_masks.reserve(static_cast<size_t>(std::min(detections.count, YOLO_BACKEND_MAX_RESULTS)));
#else
results->count = std::min(detections.count, YOLO_BACKEND_MAX_RESULTS);
#endif
const int source_count = std::min(detections.count, YOLO_BACKEND_MAX_RESULTS);
for (int index = 0; index < source_count; ++index) {
const auto& source = detections.results[index];
#if defined(YOLO_BACKEND_SEG)
unsigned char* full_mask = detections.results_seg[index].seg_mask;
const auto bounds = yolo_find_mask_bounds(full_mask, image->width, image->height, image->width);
auto mask_roi = yolo_copy_binary_mask_roi(full_mask, image->width, bounds);
std::free(full_mask);
detections.results_seg[index].seg_mask = nullptr;
if (bounds.empty() || mask_roi.empty()) continue;
backend->segmentation_masks.push_back(std::move(mask_roi));
auto& result = results->results[results->count++];
#else
auto& result = results->results[index];
#endif
result.task = results->task;
result.class_id = source.cls_id;
result.score = source.prop;
std::snprintf(result.label, sizeof(result.label), "class_%d", source.cls_id);
set_box(result, source.box);
#if defined(YOLO_BACKEND_SEG)
auto& mask = backend->segmentation_masks.back();
result.mask_bbox.x = static_cast<float>(bounds.x);
result.mask_bbox.y = static_cast<float>(bounds.y);
result.mask_bbox.width = static_cast<float>(bounds.width);
result.mask_bbox.height = static_cast<float>(bounds.height);
result.mask_data = mask.data();
result.mask_width = bounds.width;
result.mask_height = bounds.height;
result.mask_stride_bytes = bounds.width;
result.mask_origin_x = bounds.x;
result.mask_origin_y = bounds.y;
#elif defined(YOLO_BACKEND_POSE)
result.keypoint_count = 17;
for (int keypoint = 0; keypoint < result.keypoint_count; ++keypoint) {
result.keypoints[keypoint] = { source.keypoints[keypoint][0], source.keypoints[keypoint][1], source.keypoints[keypoint][2] };
}
#endif
}
#if defined(YOLO_BACKEND_SEG)
for (int index = source_count; index < detections.count; ++index) {
std::free(detections.results_seg[index].seg_mask);
detections.results_seg[index].seg_mask = nullptr;
}
#endif
}
#endif
const double elapsed = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
results->performance.total_ms = elapsed;
yolo_adapter_get_performance(&results->performance.preprocess_ms, &results->performance.inference_ms, &results->performance.postprocess_ms);
return status == 0 ? YOLO_STATUS_OK : YOLO_STATUS_BACKEND_FAILED;
}
extern "C" const char* yolo_backend_last_error(const yolo_runtime_backend_t* backend) {
(void)backend;
return "RKNN backend did not provide detailed error information";
}
extern "C" void yolo_backend_destroy(yolo_runtime_backend_t* backend) {
if (!backend) return;
if (backend->initialized) {
#if defined(YOLO_BACKEND_SEG)
release_yolov8_seg_model(&backend->context);
#elif defined(YOLO_BACKEND_CLS)
release_mobilenet_model(&backend->context);
#elif defined(YOLO_BACKEND_POSE)
release_yolov8_pose_model(&backend->context);
#elif defined(YOLO_BACKEND_OBB)
release_yolov8_obb_model(&backend->context);
#else
release_yolov8_model(&backend->context);
#endif
}
delete backend;
}