yolov8seg Implementación de Rockchip RKNN

El primero es convertir el modelo onnx de yolov8 al modelo rknn, aquí está yolov8n-seg.
El código del modelo de conversión es el siguiente, este es el código de Python:

if __name__ == '__main__':

    platform = 'rkXXXX' #写自己的型号
    exp = 'yolov8n_seg'
    Width = 640
    Height = 640
    MODEL_PATH = './onnx_models/yolov8n-seg.onnx'
    NEED_BUILD_MODEL = True
    # NEED_BUILD_MODEL = False
    im_file = './dog_bike_car_640x640.jpg'

    # Create RKNN object
    rknn = RKNN(verbose=False)

    OUT_DIR = "rknn_models"
    RKNN_MODEL_PATH = '{}/{}.rknn'.format(OUT_DIR,exp)
    if NEED_BUILD_MODEL:
        DATASET = './dataset.txt'
        rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rkXXXX")
        # Load model
        print('--> Loading model')
        ret = rknn.load_onnx(MODEL_PATH)
        if ret != 0:
            print('load model failed!')
            exit(ret)
        print('done')

        # Build model
        print('--> Building model')
        ret = rknn.build(do_quantization=True, dataset=DATASET)
        if ret != 0:
            print('build model failed.')
            exit(ret)
        print('done')

        # Export rknn model
        if not os.path.exists(OUT_DIR):
            os.mkdir(OUT_DIR)
        print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH))
        ret = rknn.export_rknn(RKNN_MODEL_PATH)
        if ret != 0:
            print('Export rknn model failed.')
            exit(ret)
        print('done')
    else:
        ret = rknn.load_rknn(RKNN_MODEL_PATH)

    rknn.release()

Después de ejecutarlo correctamente, obtendrá el modelo yolov8n_seg.rknn.

A continuación se muestra el código cpp.
Ejecute el modelo rknn, use este código si no es copia cero.

//设置outputs数组保存结果
rknn_output outputs[io_num.n_output];  //长度为2的数组
memset(outputs, 0, sizeof(outputs));
for (int i = 0; i < io_num.n_output; i++) {
    
    
  outputs[i].want_float = 0;
}

ret = rknn_run(ctx, NULL); //运行rknn模型
//这里不是零copy方法,需要用到rknn_outputs_get函数
ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL);

El método de copia cero utiliza este código.

ret = rknn_run(ctx, nullptr);

memcpy(y0, output_mems[0]->virt_addr, output_attrs[0].n_elems * sizeof(char)); //outputs[0]copy到y0
memcpy(y1, output_mems[1]->virt_addr, output_attrs[1].n_elems * sizeof(char));

Echemos un vistazo a la estructura de las salidas de los resultados de la operación del modelo rknn.
Para obtener detalles sobre cómo se produjo este resultado y su significado, consulte aquí .
El tamaño de las salidas es 2, donde el tamaño de las salidas [0] es 8400*176, 8400 es el número de cajas,
176 contiene 3 partes, 64 son las coordenadas de la caja, 80 es el problema de categoría, 32 es el coeficiente de máscara.
salidas [1] Es proto_type y su tamaño es 32 * 160 * 160.

want_float es si el resultado se genera como flotante. Aquí, establezca 0 para indicar que todavía es una salida int y luego descuantifiquelo más tarde.
La parte de datos se almacena en buf, que es (void*), y se convierte a (int8_t*) cuando se usa.

Por favor agregue una descripción de la imagen.

Ahora necesitamos escribir el posprocesamiento para obtener el cuadro de destino y enmascarar las salidas.

Las ideas principales son las siguientes:

1. Descuantización

La salida del modelo rknn es del tipo int8_t cuantificado y debe descuantificarse a un tipo flotante durante el cálculo.
Primero, veamos cómo se cuantifica el modelo rknn.

  rknn_tensor_attr output_attrs[io_num.n_output];
  memset(output_attrs, 0, sizeof(output_attrs));
  for (int i = 0; i < io_num.n_output; i++) {
    
    
    output_attrs[i].index = i;
    //rknn_query:查询模型与sdk的相关信息
    //output_attrs:存放返回结果的结构体变量
    ret                   = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
    dump_tensor_attr(&(output_attrs[i]));
  }

Los resultados del volcado son los siguientes: puede ver que el tipo de salida del modelo es int8 y la cuantificación utiliza transformación afín,
es decir, salida / escala + zp.

Por favor agregue una descripción de la imagen.

Entonces (salida - zp) * se requiere escala para la cuantificación inversa

//反量化计算
static float deqnt_affine_to_f32(int8_t qnt, int32_t zp, float scale) {
    
     return ((float)qnt - (float)zp) * scale; }

2. parte de la caja

8400 * 176, de los cuales 8400 es el número de cajas, 176 contiene 3 partes, 64 son las coordenadas de la caja, 80 es el problema de categoría, 32 es el coeficiente de máscara. Las 8400 partes de la caja eliminan 80 problemas de categoría de cada caja,
y seleccione el más grande El problema de categoría es la etiqueta de este cuadro.
Cuando el problema de categoría> umbral, es válido. Los
primeros 64 datos en cada 176 vectores, cada grupo de 16, un total de 4 grupos, pueden obtener el cuadro (l , t, r, b). Utilice
los puntos centrales de 8400 anclajes y (l, t, r, b) para obtener (x0, y0, x1, y1) de la caja.

Además, guarde el coeficiente de máscara de 32 dimensiones de cada caja.

Tenga en cuenta que al juzgar si el problema de categoría es > umbral, primero se debe descuantificar el problema de categoría.

for (int i = 0; i < box_num; i++) {
    
     //遍历8400个box
    ...
    // find label with max score
	int label = -1;
	float score = -FLT_MAX;
	//找到最大score和对应的label
	for (int k = 0; k < num_class; k++)  //80个类别prob
	{
    
    
	    float confidence = deqnt_affine_to_f32(score_ptr[k], zp, scale);  //反量化,int转float
	    if (confidence > score)
	    {
    
    
	        label = k;
	        score = confidence;
	    }
	}
	float box_prob = sigmoid(score); 
	       
	if (box_prob >= prob_threshold){
    
    
        ... 
    	softmax(bbox_pred);  //对4x16中的每16个一组求softmax,因为作了softmax,就不再需要对每个数据作反量化
    	...
		//anchor中心点
		float pb_cx = (grid_strides[i].grid0 + 0.5f) * grid_strides[i].stride;
		float pb_cy = (grid_strides[i].grid1 + 0.5f) * grid_strides[i].stride;
		
		float x0 = pb_cx - pred_ltrb[0]; //center_x - l
		float y0 = pb_cy - pred_ltrb[1]; //center_y - t
		float x1 = pb_cx + pred_ltrb[2]; //center_x + r
		float y1 = pb_cy + pred_ltrb[3]; //center_y + b
		
		Object obj;
		obj.rect.x = x0;
		obj.rect.y = y0;
		obj.rect.width = x1 - x0;
		obj.rect.height = y1 - y0;
		obj.label = label;
		obj.prob = box_prob;
		obj.mask_feat.resize(32);
		
		std::copy(bbox_ptr + 64 + num_class, bbox_ptr + 64 + num_class + 32, obj.mask_feat.begin());
		objects.push_back(obj);
	}
}

Lo anterior es filtrar la primera ola de cuadros con el umbral de la categoría prob
y luego filtrar otra ola con nms.

nms_sorted_bboxes(proposals, picked, nms_threshold); //picked里面保存的是proposals的下标

3. Parte de la máscara

La máscara se obtiene mediante la multiplicación matricial de mask_coeff y proto_type.
mask_coeff se almacena en mask_feat de cada obj en los objetos anteriores.
Tenga en cuenta que el mask_feat anterior no ha sido descuantificado.

salidas [1] es el puntero al comienzo de los datos.
Para facilitar operaciones posteriores, como la multiplicación de matrices y el cambio de tamaño, es necesario convertirlo a cv::Mat.

Tenga en cuenta que si es un Mat de tipo int8, limitará automáticamente el valor a [0,255]
y, obviamente, hay números negativos en mask_feat, así que use un Mat de tipo flotante .

Primero convierta el mask_coeff del objetivo seleccionado por nms a Mat. Luego realice cálculos de cuantificación inversa.

//mask_feat里面保存每个目标的mask coeff
cv::Mat mask_feat_qnt(count, 32, CV_32FC1); //float型的Mat, count为nms后目标的个数
for (int i = 0; i < count; i++) {
    
    
    float* mask_feat_ptr = mask_feat_qnt.ptr<float>(i);  //指向第i行的指针
    std::memcpy(mask_feat_ptr, proposals[picked[i]].mask_feat.data(), sizeof(float) * proposals[picked[i]].mask_feat.size());
}

//因为mask_feat从outputs[0]中得到,所以用zps[0],scales[0]作反量化
cv::Mat mask_feat = (mask_feat_qnt - out_zps[0]) * out_scales[0];

Convierta proto_type a cv::Mat. El valor en proto_type también se descuantifica.

cv::Mat proto = cv::Mat(32, 25600, CV_32FC1, proto_arr);

Multiplique la matriz mask_coeff y proto_type para obtener la máscara del objetivo.

cv::Mat mulResMask = (mask_feat * proto).t(); //(n,32)*(32,25600)=n*25600,必须加转置,不加的话mask会很奇怪
cv::Mat masks = mulResMask.reshape(count, {
    
    160, 160}); //每个目标有一个160*160的mask 

std::vector<cv::Mat> maskChannels;
cv::split(masks, maskChannels); //把count个mask分割开,每个目标的mask保存为vector的一个元素

Guarde el cuadro y la máscara de cada objetivo en los objetos de resultado.

std::vector<Object> objects;
objects.resize(count);  //保存结果的object
 for (int i = 0; i < count; i++)
 {
    
    
     objects[i] = proposals[picked[i]];

     // adjust offset to original unpadded
     float x0 = (objects[i].rect.x - (wpad / 2)) / scale;
     float y0 = (objects[i].rect.y - (hpad / 2)) / scale;
     float x1 = (objects[i].rect.x + objects[i].rect.width - (wpad / 2)) / scale;
     float y1 = (objects[i].rect.y + objects[i].rect.height - (hpad / 2)) / scale;

     // clip
     x0 = std::max(std::min(x0, (float)(img_width - 1)), 0.f);
     y0 = std::max(std::min(y0, (float)(img_height - 1)), 0.f);
     x1 = std::max(std::min(x1, (float)(img_width - 1)), 0.f);
     y1 = std::max(std::min(y1, (float)(img_height - 1)), 0.f);

     objects[i].rect.x = x0;
     objects[i].rect.y = y0;
     objects[i].rect.width = x1 - x0;
     objects[i].rect.height = y1 - y0;

     //计算mask并保存到object
     cv::Mat dest, mask;
     cv::exp(-maskChannels[i], dest); //每个mat单独计算sigmoid
     dest = 1.0 / (1.0 + dest);  //160x160的mask
     dest = dest(roi);  //取rect区域

     cv::resize(dest, mask, cv::Size(img_width, img_height), cv::INTER_NEAREST);

     //crop
     cv::Rect tmp_rect = objects[i].rect;
     mask = mask(tmp_rect) > 0.5; //mask阈值设为0.5
     objects[i].mask = mask;
 }

El efecto es el siguiente:

Por favor agregue una descripción de la imagen.

La versión completa del código se encuentra en semántica en github.

Supongo que te gusta

Origin blog.csdn.net/level_code/article/details/132223496
Recomendado
Clasificación