YOLO26-Seg ONNXruntime C++/python推理

python版本:

python 复制代码
import cv2
import argparse
import numpy as np
import math
import onnxruntime
from typing import Tuple

class_names = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
               'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
               'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
               'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
               'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
               'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
               'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard',
               'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
               'scissors', 'teddy bear', 'hair drier', 'toothbrush']

# Create a list of colors for each class where each color is a tuple of 3 integer values
rng = np.random.default_rng(3)
colors = rng.uniform(0, 255, size=(len(class_names), 3))

class Segment:
    def __init__(self, path, conf_thres=0.7):
        self.conf_threshold = conf_thres
        self.onnx_path = path

        self.session = onnxruntime.InferenceSession(self.onnx_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
        self.get_input_output_details()
        # 新增:存储letterbox的偏移和缩放信息
        self.letterbox_top = 0
        self.letterbox_left = 0
        self.letterbox_scale = 1.0
    
    def get_input_output_details(self):
        model_inputs = self.session.get_inputs()
        model_outputs = self.session.get_outputs()

        self.input_names = [input_.name for input_ in model_inputs]
        self.output_names = [output.name for output in model_outputs]

        self.input_shape = model_inputs[0].shape
        self.input_height = self.input_shape[2]
        self.input_width = self.input_shape[3]
    
    def letterbox(self, img: np.ndarray, new_shape: Tuple[int, int]) -> Tuple[np.ndarray, Tuple[int, int], float]:
        """
        图片缩放,等比例缩放加114补黑边
        Args:
            img: 输入图片 (H,W,C)
            new_shape: 目标尺寸 (height, width)
        Returns:
            img: 处理后的图片
            (top, left): 补边的上下左右偏移
            r: 缩放比例
        """
        shape = img.shape[:2]  # current shape [height, width]

        # 计算等比例缩放因子
        r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])

        # 计算缩放后的尺寸
        new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
        # 计算需要补充的边距
        dw, dh = (new_shape[1] - new_unpad[0]) / 2, (new_shape[0] - new_unpad[1]) / 2  # wh padding

        if shape[::-1] != new_unpad:  # resize
            img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
        
        # 计算上下左右补边
        top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
        left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
        
        # 补边(114灰度值)
        img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))

        return img, (top, left), r

    def prepare_input(self, image):
        self.img_height, self.img_width = image.shape[:2]

        input_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        # 替换原有resize:使用letterbox等比例缩放+补边
        input_img, (self.letterbox_top, self.letterbox_left), self.letterbox_scale = self.letterbox(
            input_img, (self.input_height, self.input_width)
        )

        # Scale input pixel values to 0 to 1
        input_img = input_img / 255.0
        input_img = input_img.transpose(2, 0, 1)
        input_tensor = input_img[np.newaxis, :, :, :].astype(np.float32)

        return input_tensor

    def inference(self, input_tensor):
        outputs = self.session.run(self.output_names, {self.input_names[0]: input_tensor})
        return outputs

    def process_box_output_26(self, box_output):
        detections = box_output[0]
        boxes = []
        scores = []
        class_ids = []
        mask_predictions = []

        for det in detections:
            x1, y1, x2, y2, score, class_id = det[:6]

            mask_prediction = det[6:]

            if score < self.conf_threshold:
                continue
            boxes.append([int(x1), int(y1), int(x2), int(y2)])
            scores.append(float(score))
            class_ids.append(int(class_id))
            mask_predictions.append(mask_prediction)

        boxes = np.array(boxes)
        # 修正坐标还原:需要先减去补边,再还原缩放比例
        if len(boxes) > 0:
            # 第一步:减去letterbox补的边
            boxes[:, [0, 2]] -= self.letterbox_left  # x1, x2 减左补边
            boxes[:, [1, 3]] -= self.letterbox_top   # y1, y2 减上补边
            # 第二步:还原到原始图片尺寸(除以缩放比例)
            boxes = boxes / self.letterbox_scale
        
        scores = np.array(scores)
        class_ids = np.array(class_ids)
        mask_predictions = np.array(mask_predictions)

        return boxes, scores, class_ids, mask_predictions

    def segment_objects(self, image):
        input_tensor = self.prepare_input(image)
        outputs = self.inference(input_tensor)

        self.boxes, self.scores, self.class_ids, mask_pred = self.process_box_output_26(outputs[0])

        self.mask_maps = self.process_mask_output(mask_pred, outputs[1])

        return self.boxes, self.scores, self.class_ids, self.mask_maps

    def process_mask_output(self, mask_predictions, mask_output):
        if mask_predictions.shape[0] == 0:
            return []

        mask_output = np.squeeze(mask_output)
        num_mask, mask_height, mask_width = mask_output.shape  # CHW
        
        # 计算mask
        masks = sigmoid(mask_predictions @ mask_output.reshape((num_mask, -1)))
        masks = masks.reshape((-1, mask_height, mask_width))

        # 修正mask坐标缩放:基于letterbox的缩放比例和补边
        # 计算mask尺寸到letterbox尺寸的缩放比
        mask2letter_x = self.input_width / mask_width
        mask2letter_y = self.input_height / mask_height
        
        # 计算letterbox到原图的缩放比(就是1/letterbox_scale)
        letter2orig_x = 1.0 / self.letterbox_scale
        letter2orig_y = 1.0 / self.letterbox_scale

        mask_maps = np.zeros((len(self.boxes), self.img_height, self.img_width))
        blur_size = (int(self.img_width / (mask_width * self.letterbox_scale)), 
                     int(self.img_height / (mask_height * self.letterbox_scale)))

        for i in range(len(self.boxes)):
            # 获取原始坐标
            x1, y1, x2, y2 = self.boxes[i].astype(int)
            
            # 计算mask上的对应坐标(反向计算)
            letter_x1 = int((x1 * self.letterbox_scale) + self.letterbox_left)
            letter_y1 = int((y1 * self.letterbox_scale) + self.letterbox_top)
            letter_x2 = int((x2 * self.letterbox_scale) + self.letterbox_left)
            letter_y2 = int((y2 * self.letterbox_scale) + self.letterbox_top)
            
            # 转换到mask尺寸
            scale_x1 = max(0, int(letter_x1 / mask2letter_x))
            scale_y1 = max(0, int(letter_y1 / mask2letter_y))
            scale_x2 = min(mask_width, int(letter_x2 / mask2letter_x))
            scale_y2 = min(mask_height, int(letter_y2 / mask2letter_y))

            if scale_x1 >= scale_x2 or scale_y1 >= scale_y2:
                continue

            # 裁剪并缩放mask
            scale_crop_mask = masks[i][scale_y1:scale_y2, scale_x1:scale_x2]
            crop_mask = cv2.resize(scale_crop_mask, (x2 - x1, y2 - y1), interpolation=cv2.INTER_CUBIC)

            crop_mask = cv2.blur(crop_mask, blur_size)
            crop_mask = (crop_mask > 0.5).astype(np.uint8)
            
            # 确保坐标在图片范围内
            y1 = max(0, y1)
            y2 = min(self.img_height, y2)
            x1 = max(0, x1)
            x2 = min(self.img_width, x2)
            
            mask_maps[i, y1:y2, x1:x2] = crop_mask

        return mask_maps

    def rescale_boxes(self, boxes, input_shape, image_shape):
        """兼容原有接口,实际已在process_box_output_26中完成坐标还原"""
        return boxes

def draw_detections(image, boxes, scores, class_ids, mask_maps=None, mask_alpha=0.5):
    img_height, img_width = image.shape[:2]
    size = min([img_height, img_width]) * 0.0006
    text_thickness = int(min([img_height, img_width]) * 0.001)

    mask_img = draw_masks(image, boxes, class_ids, mask_alpha, mask_maps)

    # Draw bounding boxes and labels of detections
    for box, score, class_id in zip(boxes, scores, class_ids):
        color = colors[class_id]

        x1, y1, x2, y2 = box.astype(int)
        
        # 确保坐标在图片范围内
        x1 = max(0, x1)
        y1 = max(0, y1)
        x2 = min(img_width, x2)
        y2 = min(img_height, y2)

        # Draw rectangle
        cv2.rectangle(mask_img, (x1, y1), (x2, y2), color, 2)

        label = class_names[class_id]
        caption = f'{label} {int(score * 100)}%'
        (tw, th), _ = cv2.getTextSize(text=caption, fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                                      fontScale=size, thickness=text_thickness)
        th = int(th * 1.2)

        cv2.rectangle(mask_img, (x1, y1), (x1 + tw, y1 - th), color, -1)

        cv2.putText(mask_img, caption, (x1, y1),
                    cv2.FONT_HERSHEY_SIMPLEX, size, (255, 255, 255), text_thickness, cv2.LINE_AA)

    return mask_img

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def draw_masks(image, boxes, class_ids, mask_alpha=0.3, mask_maps=None):
    mask_img = image.copy()

    # Draw bounding boxes and labels of detections
    for i, (box, class_id) in enumerate(zip(boxes, class_ids)):
        color = colors[class_id]

        x1, y1, x2, y2 = box.astype(int)
        
        # 确保坐标在图片范围内
        x1 = max(0, x1)
        y1 = max(0, y1)
        x2 = min(image.shape[1], x2)
        y2 = min(image.shape[0], y2)

        # Draw fill mask image
        if mask_maps is None:
            cv2.rectangle(mask_img, (x1, y1), (x2, y2), color, -1)
        else:
            crop_mask = mask_maps[i][y1:y2, x1:x2, np.newaxis]
            crop_mask_img = mask_img[y1:y2, x1:x2]
            crop_mask_img = crop_mask_img * (1 - crop_mask) + crop_mask * color
            mask_img[y1:y2, x1:x2] = crop_mask_img

    return cv2.addWeighted(mask_img, mask_alpha, image, 1 - mask_alpha, 0)

if __name__ == '__main__':
    # 初始化推理器
    yoloseg = Segment("weights/yolo26s-seg.onnx", conf_thres=0.5)
    
    # 读取图片
    img = cv2.imread("bus.jpg")
    if img is None:
        print("Error: 无法读取图片文件")
    else:
        # 推理
        boxes, scores, class_ids, masks = yoloseg.segment_objects(img)
        
        # 绘制结果
        combined_img = draw_detections(img, boxes, scores, class_ids, masks)
        
        # 保存结果
        cv2.imwrite("output.jpg", combined_img)
        print("推理完成,结果已保存为 output.jpg")

C++版本:

python 复制代码
// 1. 必须放在最第一行,解决 strdup 等函数的安全警告
#define _CRT_SECURE_NO_WARNINGS 

#include <iostream>
#include <vector>
#include <string>
#include <random>
#include <algorithm>
#include <opencv2/opencv.hpp>
#include <onnxruntime_cxx_api.h>

// 2. 解决跨平台 strdup 问题
#ifdef _WIN32
#define MY_STRDUP _strdup
#else
#define MY_STRDUP strdup
#endif

using namespace cv;
using namespace std;

// 类别名称
const std::vector<std::string> class_names = {
	"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
	"fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
	"elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
	"skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
	"tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
	"sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
	"potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard",
	"cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase",
	"scissors", "teddy bear", "hair drier", "toothbrush"
};

// 颜色生成
std::vector<cv::Scalar> generateColors(int num_classes) {
	std::vector<cv::Scalar> colors;
	std::mt19937 rng(3);
	std::uniform_int_distribution<int> uni(0, 255);
	for (int i = 0; i < num_classes; ++i) {
		colors.push_back(cv::Scalar(uni(rng), uni(rng), uni(rng)));
	}
	return colors;
}

static std::vector<cv::Scalar> colors = generateColors(class_names.size());

struct Detection {
	cv::Rect box;       // xywh (integer)
	float score;
	int class_id;
	std::vector<float> mask_coeffs; // 32维系数
};

class Segment {
public:
	Segment(const std::string& model_path, float conf_thres = 0.7);

	// ============ 修改点:析构函数必须是 public ============
	~Segment() {
		for (auto ptr : input_names) free(ptr);
		for (auto ptr : output_names) free(ptr);
	}
	// ====================================================

	void run(const cv::Mat& image, std::vector<Detection>& detections, std::vector<cv::Mat>& masks);
	cv::Mat draw(const cv::Mat& image, const std::vector<Detection>& detections, const std::vector<cv::Mat>& masks);

private:
	Ort::Env env;
	Ort::Session session;
	Ort::AllocatorWithDefaultOptions allocator;

	std::vector<char*> input_names;
	std::vector<char*> output_names;

	int input_h;
	int input_w;
	float conf_threshold;

	// Letterbox 参数记录
	int lb_top = 0;
	int lb_left = 0;
	float lb_scale = 1.0f;
	int img_h = 0;
	int img_w = 0;

	void letterbox(const cv::Mat& src, cv::Mat& dst);
};

Segment::Segment(const std::string& model_path, float conf_thres)
	: env(ORT_LOGGING_LEVEL_WARNING, "YoloSeg"),
	session(nullptr),
	conf_threshold(conf_thres)
{
	Ort::SessionOptions session_options;
	session_options.SetIntraOpNumThreads(1);
	session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
	// session_options.AppendExecutionProvider_CUDA(0); // 如果有CUDA请取消注释

	// 3. 解决 Windows 路径宽字符问题
#ifdef _WIN32
	std::wstring wide_model_path = std::wstring(model_path.begin(), model_path.end());
	session = Ort::Session(env, wide_model_path.c_str(), session_options);
#else
	session = Ort::Session(env, model_path.c_str(), session_options);
#endif

	// 获取输入输出信息
	size_t num_input_nodes = session.GetInputCount();
	for (size_t i = 0; i < num_input_nodes; i++) {
		// 使用 MY_STRDUP (_strdup)
		input_names.push_back(MY_STRDUP(session.GetInputNameAllocated(i, allocator).get()));
	}
	size_t num_output_nodes = session.GetOutputCount();
	for (size_t i = 0; i < num_output_nodes; i++) {
		output_names.push_back(MY_STRDUP(session.GetOutputNameAllocated(i, allocator).get()));
	}

	// 获取输入尺寸 (假设是 [1, 3, H, W])
	auto input_shape = session.GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
	input_h = input_shape[2];
	input_w = input_shape[3];
}

void Segment::letterbox(const cv::Mat& src, cv::Mat& dst) {
	int h = src.rows;
	int w = src.cols;
	this->img_h = h;
	this->img_w = w;

	// 计算缩放比例 (等比例)
	float r = std::min((float)input_h / h, (float)input_w / w);
	this->lb_scale = r;

	int new_unpad_w = (int)std::round(w * r);
	int new_unpad_h = (int)std::round(h * r);

	int dw = input_w - new_unpad_w;
	int dh = input_h - new_unpad_h;

	// 居中补边
	this->lb_left = (int)std::round(dw / 2.0 - 0.1);
	int right = input_w - new_unpad_w - this->lb_left;
	this->lb_top = (int)std::round(dh / 2.0 - 0.1);
	int bottom = input_h - new_unpad_h - this->lb_top;

	cv::Mat resized;
	if (h != new_unpad_h || w != new_unpad_w) {
		cv::resize(src, resized, cv::Size(new_unpad_w, new_unpad_h));
	}
	else {
		resized = src;
	}

	cv::copyMakeBorder(resized, dst, lb_top, bottom, lb_left, right, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114));
}

void Segment::run(const cv::Mat& image, std::vector<Detection>& detections, std::vector<cv::Mat>& masks) {
	detections.clear();
	masks.clear();

	// 1. 预处理
	cv::Mat input_img;
	letterbox(image, input_img);

	cv::Mat blob;
	cv::dnn::blobFromImage(input_img, blob, 1.0 / 255.0, cv::Size(), cv::Scalar(), true, false);

	// 2. 推理
	std::vector<int64_t> input_dims = { 1, 3, input_h, input_w };
	Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
	Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
		memory_info, blob.ptr<float>(), blob.total(), input_dims.data(), input_dims.size());

	// 注意:Run 函数需要 const char* const* 类型的 input_names
	auto outputs = session.Run(Ort::RunOptions{ nullptr },
		(const char* const*)input_names.data(), &input_tensor, 1,
		(const char* const*)output_names.data(), output_names.size());

	// 3. 解析 Output0 (Boxes)
	// 假设 Output0 形状: [1, Num_Dets, 6 + 32] -> [1, N, 38]
	float* box_data = outputs[0].GetTensorMutableData<float>();
	auto box_shape = outputs[0].GetTensorTypeAndShapeInfo().GetShape();
	int num_dets = box_shape[1];
	int dim = box_shape[2]; // 38

	std::vector<std::vector<float>> mask_coeffs_list;

	for (int i = 0; i < num_dets; i++) {
		float* ptr = box_data + i * dim;
		float score = ptr[4];

		if (score < conf_threshold) continue;

		Detection det;
		// 直接读取坐标 (x1, y1, x2, y2)
		float x1 = ptr[0];
		float y1 = ptr[1];
		float x2 = ptr[2];
		float y2 = ptr[3];

		det.class_id = (int)ptr[5];
		det.score = score;

		// 坐标还原 (减去padding -> 除以缩放)
		x1 = (x1 - lb_left) / lb_scale;
		y1 = (y1 - lb_top) / lb_scale;
		x2 = (x2 - lb_left) / lb_scale;
		y2 = (y2 - lb_top) / lb_scale;

		det.box = cv::Rect((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1));

		// 读取 Mask 系数 (从索引6开始的32个值)
		det.mask_coeffs.assign(ptr + 6, ptr + 6 + 32);

		detections.push_back(det);
		mask_coeffs_list.push_back(det.mask_coeffs);
	}

	// 4. 解析 Output1 (Mask Protos) 并生成 Mask
	if (detections.empty()) return;

	// Output1 Shape: [1, 32, 160, 160] (假设)
	float* proto_data = outputs[1].GetTensorMutableData<float>();
	auto proto_shape = outputs[1].GetTensorTypeAndShapeInfo().GetShape();
	int mask_c = proto_shape[1]; // 32
	int mask_h = proto_shape[2]; // 160
	int mask_w = proto_shape[3]; // 160

	// 将 Protos 视为矩阵 [32, 160*160]
	cv::Mat protos(mask_c, mask_h * mask_w, CV_32F, proto_data);

	// 将所有检测的系数堆叠为矩阵 [Num_Dets, 32]
	cv::Mat coeffs(detections.size(), mask_c, CV_32F);
	for (size_t i = 0; i < detections.size(); ++i) {
		memcpy(coeffs.ptr<float>(i), mask_coeffs_list[i].data(), mask_c * sizeof(float));
	}

	// 矩阵乘法: [N, 32] * [32, 25600] = [N, 25600]
	cv::Mat mask_result_flat = coeffs * protos;

	// 计算缩放比例 (Mask -> Letterbox)
	float mask2letter_x = (float)input_w / mask_w;
	float mask2letter_y = (float)input_h / mask_h;

	// 模糊核大小
	int blur_w = (int)(img_w / (mask_w * lb_scale));
	int blur_h = (int)(img_h / (mask_h * lb_scale));
	if (blur_w < 1) blur_w = 1;
	if (blur_h < 1) blur_h = 1;

	for (size_t i = 0; i < detections.size(); ++i) {
		// 获取当前检测的 mask (1x25600) -> reshape (160x160)
		cv::Mat single_mask = mask_result_flat.row(i).reshape(1, mask_h); // 160x160

		// Sigmoid
		cv::Mat sigmoid_mask;
		cv::exp(-single_mask, sigmoid_mask);
		sigmoid_mask = 1.0 / (1.0 + sigmoid_mask);

		// 获取还原后的 Box 坐标
		int x1 = detections[i].box.x;
		int y1 = detections[i].box.y;
		int x2 = x1 + detections[i].box.width;
		int y2 = y1 + detections[i].box.height;

		// 反算回 Letterbox 坐标系
		int letter_x1 = (int)(x1 * lb_scale + lb_left);
		int letter_y1 = (int)(y1 * lb_scale + lb_top);
		int letter_x2 = (int)(x2 * lb_scale + lb_left);
		int letter_y2 = (int)(y2 * lb_scale + lb_top);

		// 映射到 Mask 坐标系 (160x160)
		int scale_x1 = std::max(0, (int)(letter_x1 / mask2letter_x));
		int scale_y1 = std::max(0, (int)(letter_y1 / mask2letter_y));
		int scale_x2 = std::min(mask_w, (int)(letter_x2 / mask2letter_x));
		int scale_y2 = std::min(mask_h, (int)(letter_y2 / mask2letter_y));

		if (scale_x2 <= scale_x1 || scale_y2 <= scale_y1) {
			masks.push_back(cv::Mat::zeros(img_h, img_w, CV_8UC1)); // 空 Mask
			continue;
		}

		// 裁剪 Mask
		cv::Rect crop_rect(scale_x1, scale_y1, scale_x2 - scale_x1, scale_y2 - scale_y1);
		cv::Mat crop_mask = sigmoid_mask(crop_rect);

		// Resize 到物体在原图中的大小
		int target_w = x2 - x1;
		int target_h = y2 - y1;

		if (target_w <= 0 || target_h <= 0) {
			masks.push_back(cv::Mat::zeros(img_h, img_w, CV_8UC1));
			continue;
		}

		cv::Mat resized_mask;
		cv::resize(crop_mask, resized_mask, cv::Size(target_w, target_h), 0, 0, cv::INTER_CUBIC);

		// Blur
		cv::blur(resized_mask, resized_mask, cv::Size(blur_w, blur_h));

		// 二值化
		cv::Mat binary_mask;
		cv::compare(resized_mask, 0.5, binary_mask, cv::CMP_GT);

		// 放入全图大小的 Mask 中
		cv::Mat full_mask = cv::Mat::zeros(img_h, img_w, CV_8UC1);

		// 确保边界安全
		x1 = std::max(0, x1); y1 = std::max(0, y1);
		x2 = std::min(img_w, x2); y2 = std::min(img_h, y2);

		int roi_w = std::min(x2 - x1, resized_mask.cols);
		int roi_h = std::min(y2 - y1, resized_mask.rows);

		if (roi_w > 0 && roi_h > 0) {
			cv::Rect roi_rect(x1, y1, roi_w, roi_h);
			cv::Rect mask_rect(0, 0, roi_w, roi_h);
			binary_mask(mask_rect).copyTo(full_mask(roi_rect));
		}

		masks.push_back(full_mask);
	}
}

cv::Mat Segment::draw(const cv::Mat& image, const std::vector<Detection>& detections, const std::vector<cv::Mat>& masks) {
	cv::Mat mask_img = image.clone();
	float mask_alpha = 0.5;

	// 绘制 Masks
	for (size_t i = 0; i < detections.size(); ++i) {
		if (masks[i].empty()) continue;

		cv::Scalar color = colors[detections[i].class_id % colors.size()];

		// 创建彩色遮罩
		cv::Mat color_mask(image.size(), image.type(), color);

		// 将原图和纯色遮罩混合
		cv::Mat blended;
		cv::addWeighted(mask_img, 1.0 - mask_alpha, color_mask, mask_alpha, 0, blended);

		// 只把 mask 区域的混合结果复制回去
		blended.copyTo(mask_img, masks[i]);
	}

	// 绘制边框和文字
	for (const auto& det : detections) {
		cv::Scalar color = colors[det.class_id % colors.size()];
		int x1 = det.box.x;
		int y1 = det.box.y;
		int x2 = x1 + det.box.width;
		int y2 = y1 + det.box.height;

		// 限制在图像内
		x1 = std::max(0, x1); y1 = std::max(0, y1);
		x2 = std::min(image.cols, x2); y2 = std::min(image.rows, y2);

		cv::rectangle(mask_img, cv::Point(x1, y1), cv::Point(x2, y2), color, 2);

		std::string label = class_names[det.class_id] + " " + std::to_string((int)(det.score * 100)) + "%";

		int baseLine;
		cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.6, 1, &baseLine);
		int top = std::max(y1, labelSize.height);

		cv::rectangle(mask_img, cv::Point(x1, top - labelSize.height),
			cv::Point(x1 + labelSize.width, top + baseLine), color, cv::FILLED);
		cv::putText(mask_img, label, cv::Point(x1, top), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255, 255, 255), 1);
	}

	return mask_img;
}

int main(int argc, char** argv) {
	// 默认路径
	std::string model_path = "yolo26n-seg.onnx";
	std::string img_path = "bus.jpg";

	if (argc > 1) model_path = argv[1];
	if (argc > 2) img_path = argv[2];

	try {
		std::cout << "Loading model: " << model_path << std::endl;
		Segment segmentor(model_path, 0.5); // conf_thres

		cv::Mat img = cv::imread(img_path);
		if (img.empty()) {
			std::cerr << "Error: Could not read image " << img_path << std::endl;
			return -1;
		}

		std::vector<Detection> detections;
		std::vector<cv::Mat> masks;

		std::cout << "Running inference..." << std::endl;
		segmentor.run(img, detections, masks);

		std::cout << "Drawing results..." << std::endl;
		cv::Mat result_img = segmentor.draw(img, detections, masks);

		cv::imwrite("output.jpg", result_img);
		std::cout << "Inference finished. Saved to output.jpg" << std::endl;

	}
	catch (const std::exception& e) {
		std::cerr << "Exception: " << e.what() << std::endl;
		return -1;
	}

	return 0;
}
相关推荐
YMWM_3 小时前
如何将包路径添加到conda环境lerobot的python路径中呢?
人工智能·python·conda
靓仔建3 小时前
Vue3导入组件出错does not provide an export named ‘user_setting‘ (at index.vue:180:10)
开发语言·前端·typescript
田里的水稻3 小时前
ubuntu22.04_openclaw_ROS2
人工智能·python·机器人
梁正雄3 小时前
Python前端-2-css练习
前端·css·python
仰泳的熊猫4 小时前
题目2194:蓝桥杯2018年第九届真题-递增三元组
数据结构·c++·算法
wefly20174 小时前
开发者效率神器!jsontop.cn一站式工具集,覆盖开发全流程高频需求
前端·后端·python·django·flask·前端开发工具·后端开发工具
2301_803554524 小时前
linux 以及 c++编程里对于进程,线程的操作
linux·运维·c++
赶路人儿4 小时前
UTC时间和时间戳介绍
java·开发语言
6+h4 小时前
【java】基本数据类型与包装类:拆箱装箱机制
java·开发语言·python