236 lines
13 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "bmp_loader.h"
#include "result_json.h"
#include "yolo_runtime.h"
#include "mask_dilation.h"
#include <iostream>
#include <memory>
#include <string_view>
#include <vector>
#include <limits>
static const char* value_after(int argc, char** argv, std::string_view option) {
for (int index = 1; index + 1 < argc; ++index)
if (argv[index] == option) return argv[index + 1];
return nullptr;
}
struct RuntimeDeleter {
void operator()(yolo_runtime_t* value) const { yolo_runtime_destroy(value); }
};
struct ResultDeleter {
void operator()(yolo_result_set_t* value) const { yolo_result_set_release(value); }
};
struct MaskBuffers {
std::vector<unsigned char*> data;
explicit MaskBuffers(size_t count):data(count,nullptr) {}
~MaskBuffers() {for(auto* p:data) free(p);}
};
int main(int argc, char** argv) {
int dilation_radius=0;
if(parse_mask_dilation(argc,argv,&dilation_radius)!=0) {std::cerr<<"Invalid --mask-dilation: integer 0..100 required\n";return 2;}
const char* config = value_after(argc, argv, "--config");
const char* image_path = value_after(argc, argv, "--image");
const char* json_path = value_after(argc, argv, "--json");
const char* geometry_name = value_after(argc, argv, "--seg-geometry");
if (!config || !image_path || !json_path) {
std::cerr << "Usage: yolo_cpp_example --config model.yaml --image sample.bmp "
"--json result.json [--seg-geometry rect|circle|mean|fit] [--rgb-mask] [--mask-dilation 0..100] "
"[--roi-lt L T | --roi-whlt W H L T]\n";
return 2;
}
if(geometry_name && std::string_view(geometry_name)!="rect" && std::string_view(geometry_name)!="circle" &&
std::string_view(geometry_name)!="mean" && std::string_view(geometry_name)!="fit") return 2;
if (yolo_runtime_get_abi_version() != YOLO_RUNTIME_ABI_VERSION) return 3;
unsigned char* raw_pixels = nullptr;
int width = 0, height = 0;
if (load_bmp_rgb(image_path, &raw_pixels, &width, &height) != 0) return 4;
std::unique_ptr<unsigned char, decltype(&free_bmp_rgb)> pixels(raw_pixels, free_bmp_rgb);
yolo_error_info_v1_t error{};
error.struct_size = sizeof(error);
error.version = YOLO_ERROR_INFO_VERSION;
yolo_runtime_t* raw_runtime = nullptr;
if (yolo_runtime_create(config, &raw_runtime, &error) != YOLO_STATUS_OK || !raw_runtime) {
std::cerr << "Create failed: " << error.message << '\n';
return 5;
}
std::unique_ptr<yolo_runtime_t, RuntimeDeleter> runtime(raw_runtime);
yolo_runtime_info_v1_t info{};
info.struct_size = sizeof(info);
info.version = YOLO_RUNTIME_INFO_VERSION;
if (yolo_runtime_get_info(runtime.get(), &info) != YOLO_STATUS_OK) return 6;
if(dilation_radius && info.task!=YOLO_TASK_SEG) return 2;
yolo_image_view_v1_t image{};
image.struct_size = sizeof(image);
image.version = YOLO_IMAGE_VIEW_ABI_VERSION;
image.data = pixels.get();
image.width = width;
image.height = height;
image.row_stride_bytes = width * 3;
image.pixel_format = YOLO_PIXEL_FORMAT_RGB888;
yolo_infer_options_v1_t options{};
options.struct_size = sizeof(options);
options.version = YOLO_INFER_OPTIONS_VERSION;
options.seg_geometry_mode = info.task == YOLO_TASK_SEG
? (geometry_name && std::string_view(geometry_name) == "circle"
? YOLO_SEG_GEOMETRY_MIN_ENCLOSING_CIRCLE
: (geometry_name && std::string_view(geometry_name)=="mean" ? YOLO_SEG_GEOMETRY_MEAN_DIAMETER_CIRCLE
: (geometry_name && std::string_view(geometry_name)=="fit" ? YOLO_SEG_GEOMETRY_ROBUST_FIT_CIRCLE : YOLO_SEG_GEOMETRY_MIN_AREA_RECT)))
: YOLO_SEG_GEOMETRY_NONE;
// ROI 默认数据:W=800(宽)、H=736(高)、L=0(左)、T=0(上),单位为原图像素。
// 默认初始化同时填写结构体大小、版本并清零保留字段,ABI 5 的字段顺序不变。
yolo_roi_v1_t roi = yolo_roi_default();
int roi_values[4] = {0};
const int roi_mode = parse_example_roi_args(argc, argv, roi_values);
int has_roi = roi_mode;
if (roi_mode == 1) {
// 只提供 L、T:保留上面的默认宽高 800×736,只修改左上角位置。
roi.x = roi_values[0]; // L:例如 --roi-lt 217 738 中的 217。
roi.y = roi_values[1]; // T:例如 --roi-lt 217 738 中的 738。
} else if (roi_mode == 2) {
// 完整指定 W、H、L、T;例如 --roi-whlt 771 609 217 738。
roi = yolo_roi_from_whlt(roi_values[0], roi_values[1], roi_values[2], roi_values[3]);
} else if (roi_mode == 0) {
// 未传 ROI 参数时沿用模型 roi_info.txt;文件不存在则全图推理。
// 默认初始化不代表无参数时自动裁剪为 800×736。
has_roi = load_model_roi(config, width, height, &roi);
}
// ROI 必须完整位于原图内;越界、非正宽高由推理接口报错,不自动裁边。
if (has_roi < 0) {
fprintf(stderr, "Invalid ROI arguments, roi_info.txt or source image dimensions\n");
return 7;
}
if (has_roi) std::cout << "ROI W=" << roi.width << " H=" << roi.height
<< " L=" << roi.x << " T=" << roi.y << '\n';
yolo_result_set_t* raw_results = nullptr;
error = {};
error.struct_size = sizeof(error);
error.version = YOLO_ERROR_INFO_VERSION;
const auto status =
has_roi ? yolo_runtime_infer_image_roi(runtime.get(), &image, &roi, &options, &raw_results, &error)
: yolo_runtime_infer_image(runtime.get(), &image, &options, &raw_results, &error);
if (status != YOLO_STATUS_OK || !raw_results) {
std::cerr << "Inference failed: " << error.message << '\n';
return 7;
}
std::unique_ptr<yolo_result_set_t, ResultDeleter> results(raw_results);
uint32_t count = 0;
if(yolo_result_set_get_count(results.get(), &count)!=YOLO_STATUS_OK) return 8;
std::cout << "task=" << info.task << " results=" << count << '\n';
// 1. Read performance and typed results directly through the public C ABI.
yolo_performance_v1_t performance{};
performance.struct_size=sizeof(performance);performance.version=YOLO_PERFORMANCE_VERSION;
if(yolo_result_set_get_performance(results.get(),&performance)!=YOLO_STATUS_OK) return 8;
std::cout << "preprocess_ms=" << performance.preprocess_ms
<< " inference_ms=" << performance.inference_ms
<< " postprocess_ms=" << performance.postprocess_ms
<< " total_ms=" << performance.total_ms << '\n';
std::vector<yolo_mask_view_v1_t> masks;
MaskBuffers owned_masks(count);
const auto region=has_roi?roi:yolo_roi_from_whlt(width,height,0,0);
for(uint32_t index=0;index<count;++index) {
yolo_result_common_v1_t common{};
common.struct_size=sizeof(common);common.version=YOLO_RESULT_COMMON_VERSION;
if(yolo_result_set_get_common(results.get(),index,&common)!=YOLO_STATUS_OK) return 8;
// Instance ID is index+1; category/class ID can be shared by many instances.
std::cout << "instance=" << index+1 << " class=" << common.class_id
<< " label=" << common.label << " score=" << common.score << '\n';
if(info.task!=YOLO_TASK_CLS) {
yolo_bbox_v1_t box{};box.struct_size=sizeof(box);box.version=YOLO_BBOX_VERSION;
if(yolo_result_set_get_bbox(results.get(),index,&box)!=YOLO_STATUS_OK) return 8;
std::cout << "bbox=" << box.x << ',' << box.y << ',' << box.width << ',' << box.height << '\n';
}
if(info.task==YOLO_TASK_OBB) {
yolo_rotated_rect_v1_t box{};box.struct_size=sizeof(box);box.version=YOLO_ROTATED_RECT_VERSION;
if(yolo_result_set_get_obb(results.get(),index,&box)!=YOLO_STATUS_OK) return 8;
std::cout << "obb center=" << box.center_x << ',' << box.center_y
<< " size=" << box.width << ',' << box.height << " radians=" << box.angle_radians << '\n';
}
if(info.task==YOLO_TASK_POSE) {
uint32_t keypoint_count=0;
if(yolo_result_set_get_pose_keypoint_count(results.get(),index,&keypoint_count)!=YOLO_STATUS_OK) return 8;
std::vector<yolo_keypoint_v1_t> keypoints(keypoint_count);
if(yolo_result_set_copy_pose_keypoints(results.get(),index,keypoints.data(),keypoint_count,&keypoint_count)!=YOLO_STATUS_OK) return 8;
for(const auto& point:keypoints)
std::cout << "keypoint=" << point.x << ',' << point.y << " score=" << point.score << '\n';
}
if(info.task==YOLO_TASK_SEG) {
yolo_seg_geometry_v1_t geometry{};
geometry.struct_size=sizeof(geometry);geometry.version=YOLO_SEG_GEOMETRY_VERSION;
if(yolo_result_set_get_seg_geometry(results.get(),index,&geometry)!=YOLO_STATUS_OK) return 8;
std::cout << "seg type=" << geometry.type << " center=" << geometry.center_x << ',' << geometry.center_y
<< " radius=" << geometry.radius << " size=" << geometry.width << ',' << geometry.height
<< " radians=" << geometry.angle_radians << " fit=" << geometry.fit_status << '\n';
// 2. Each borrowed binary mask is 0/255, with local dimensions/stride
// and an origin in the inference input image. Holes are retained.
yolo_mask_view_v1_t mask{};
mask.struct_size=sizeof(mask);mask.version=YOLO_MASK_VIEW_VERSION;
if(yolo_result_set_get_seg_mask(results.get(),index,&mask)!=YOLO_STATUS_OK) return 8;
// 独立调用公共库;原始推理掩膜和拟合几何保持原值。
if(dilate_example_mask(&mask,dilation_radius,&region,&owned_masks.data[index])!=0) return 8;
masks.push_back(mask);
std::cout << "binary mask origin=" << mask.origin_x << ',' << mask.origin_y
<< " size=" << mask.width << ',' << mask.height << " stride=" << mask.row_stride_bytes << '\n';
}
}
// 3. Every Seg run demonstrates RGB instance IDs in memory by default.
// --rgb-mask is still accepted for existing scripts, but is no longer needed.
if(info.task==YOLO_TASK_SEG) {
const uint64_t stride=uint64_t(width)*3;
const uint64_t bytes=stride*uint64_t(height);
if(stride>uint64_t(std::numeric_limits<int32_t>::max()) ||
bytes>uint64_t(std::numeric_limits<size_t>::max())) return 8;
std::vector<unsigned char> rgb(static_cast<size_t>(bytes)); // caller owns this buffer
error={};error.struct_size=sizeof(error);error.version=YOLO_ERROR_INFO_VERSION;
if(yolo_masks_compose_rgb_ids(masks.data(),count,width,height,rgb.data(),
static_cast<int32_t>(stride),bytes,&error)!=YOLO_STATUS_OK) {
std::cerr << "RGB mask failed: " << error.message << '\n';return 8;
}
// 4. Read actual pixel membership: RGB channels encode up to 3 IDs.
// Background=(0,0,0), single=(id,id,id), double=(id1,id2,id1),
// triple=(id1,id2,id3). Remove zero/duplicate channels to decode IDs.
uint64_t foreground=0,overlap_pixels=0;
std::vector<uint64_t> instance_pixels(count,0);
for(int y=0;y<height;++y) for(int x=0;x<width;++x) {
const auto* pixel=rgb.data()+size_t(y)*static_cast<size_t>(stride)+size_t(x)*3;
unsigned char ids[3]{};int id_count=0;
for(int channel=0;channel<3;++channel) {
const auto id=pixel[channel];
if(!id) continue;
bool duplicate=false;
for(int j=0;j<id_count;++j) if(ids[j]==id) duplicate=true;
if(!duplicate) {
if(id>count) return 8;
ids[id_count++]=id;++instance_pixels[id-1];
}
}
if(id_count) {
if(!foreground) {
std::cout << "RGB_PIXEL x=" << x << " y=" << y << " RGB="
<< int(pixel[0]) << ',' << int(pixel[1]) << ',' << int(pixel[2]) << " IDs=";
for(int j=0;j<id_count;++j) std::cout << (j ? "," : "") << int(ids[j]);
std::cout << '\n';
}
++foreground;if(id_count>1) ++overlap_pixels;
}
}
for(uint32_t i=0;i<count;++i) std::cout << "ID_PIXELS id=" << i+1 << " pixels=" << instance_pixels[i] << '\n';
std::cout << "RGB_ID_MASK_OK width=" << width << " height=" << height << " instances=" << count
<< " foreground=" << foreground << " overlaps=" << overlap_pixels << " bytes=" << bytes << '\n';
// Use rgb here for your processing. No BMP is needed. rgb is freed by
// vector; borrowed masks stay valid until results is released below.
}
std::cout << "MASK_DILATION radius=" << dilation_radius << '\n';
const int json_status=write_result_json_masks(json_path,config,results.get(),info.task,
info.task==YOLO_TASK_SEG?masks.data():nullptr,dilation_radius);
return json_status==0 ? 0 : 8;
}