Tome yolov8-pose como um caso para aprender a escrever a função de callback do deepstream

1. Descrição dos elementos do pipeline:

No código fornecido, os seguintes elementos são usados ​​para criar o pipeline DeepStream:

  1. source: este elemento é uma fonte de entrada para ler um fluxo de vídeo de um arquivo. Pode ser qualquer fonte de entrada de vídeo suportada pelo GStreamer, como arquivo, câmera, fluxo de rede, etc.

  2. streammux: esse elemento é um multiplexador de fluxo, usado para combinar vários fluxos em um único fluxo e agrupar vários quadros em dados em lote. Ele pode combinar streams de vídeo de diferentes fontes em uma entrada unificada.

  3. pgie: este elemento é o mecanismo de inferência primário (Primary GIE), que é usado para realizar a detecção e inferência de objetos. Ele realiza inferência com base em um determinado perfil, reconhece objetos em uma imagem e extrai suas características.

  4. nvtracker: Este elemento é o rastreador no DeepStream, que é usado para rastrear os objetos reconhecidos. Ele usa recursos de objetos previamente reconhecidos e recursos no quadro atual para correspondência e rastreamento para obter rastreamento contínuo de objetos.

  5. nvvidconv: este elemento é um conversor de vídeo para converter o formato de quadro de vídeo de NV12 para RGBA. Em alguns casos, é necessário converter quadros de vídeo de um formato para outro para acomodar diferentes elementos.

  6. nvosd: este elemento é um elemento de exibição na tela (OSD), que é usado para desenhar resultados de reconhecimento, caixas delimitadoras, rótulos e outras informações no buffer RGBA convertido.

  7. nvvidconv_postosd: este elemento é um conversor de vídeo usado para converter o quadro de vídeo do formato RGBA convertido para o formato NV12 novamente. Isso é comum antes de enviar quadros de vídeo para o codificador.

  8. caps: este elemento é o elemento Caps Filter, que é usado para definir as restrições do formato de vídeo. Ele pode especificar formatos e parâmetros específicos para fluxos de entrada ou saída para garantir a compatibilidade do fluxo.

  9. encoder: este elemento é um codificador de vídeo usado para codificar quadros de vídeo brutos em um formato de codificação de vídeo específico, como H.264 ou H.265. Ele define a taxa de bits, a qualidade da codificação etc. de acordo com os parâmetros especificados.

  10. rtppay: Este elemento é usado para empacotar dados codificados em pacotes RTP (Real-time Transport Protocol). O RTP é um protocolo de streaming em tempo real comumente usado.

  11. sink: Este elemento é um elemento UDPSink, que é usado para enviar pacotes de dados RTP para a rede através do protocolo UDP. Ele especifica onde os dados são recebidos especificando o endereço IP de destino e o número da porta.

Acima estão alguns elementos-chave usados ​​no snippet de código fornecido, que desempenham funções diferentes no pipeline DeepStream, responsáveis ​​por funções como entrada de vídeo, inferência, rastreamento, conversão, desenho e saída.

2. Construção da tubulação:

Este é um pipeline DeepStream construído com GStreamer. Vamos explicar o processo de compilação principal no código passo a passo:

  1. Primeiro, uma série de variáveis ​​e parâmetros necessários para construir o pipeline são definidos, incluindo GstElementponteiro, taxa de bits, formato de codificação, número da porta, etc.

  2. Em seguida, são criados os vários elementos do GStreamer, como source, streammux, pgie, etc. nvtracker. Esses elementos lidam com funções como entrada, inferência, rastreamento e saída de fluxos de vídeo.

  3. Os parâmetros para cada elemento são definidos. Por exemplo, defina streammuxo tamanho do lote e a resolução de saída, defina pgieo caminho do arquivo de configuração, defina nvtrackeras propriedades, etc.

  4. Adicione elementos individuais ao pipeline. Use gst_bin_add_many()funções para adicionar elementos ao pipeline do GStreamer para gerenciamento e encadeamento.

  5. Conecte o fluxo de dados entre os elementos. gst_element_link_many()Vincule elementos usando funções para definir o caminho do fluxo de dados.

  6. Adicionar sondas. Adicione testes gst_pad_add_probe()a pgie_src_pade com funções osd_sink_padpara buscar metadados e manipular buffers.

  7. Crie um servidor RTSP. Use para gst_rtsp_server_new()criar um servidor RTSP, definir o número da porta de serviço do servidor e montar o fluxo RTSP no servidor.

  8. Defina o estado do pipeline como "reproduzindo". Use gst_element_set_state()a função para definir o pipeline para o estado de reprodução, iniciando o processamento e a saída do fluxo de vídeo.

  9. Inicie o loop principal. Use g_main_loop_run()uma função para iniciar o loop principal do GStreamer, que é usado para processar eventos e mensagens.

  10. Esperando sair. Espera o loop principal terminar até que um sinal de saída seja recebido.

  11. Limpar e liberar recursos. Depois de sair do loop principal, a limpeza é feita definindo o status do pipeline como NULL, liberando recursos do pipeline e limpando outros recursos.

O acima é o processo principal do código para construir o pipeline DeepStream. Esse pipeline é usado para ler arquivos de vídeo, realizar inferência e rastreamento e, em seguida, gerar os resultados do processamento e publicá-los na rede por meio de fluxos RTSP.

3. Função principal da função de sonda pgie

As principais funções desta função de retorno de chamada Pgie são as seguintes:

  1. Obtenha o buffer do GStreamer e obtenha metadados em lote dele.

  2. Atravesse os metadados de cada quadro.

  3. Para cada quadro, itere sobre os metadados do usuário.

  4. Se o tipo de metadados do usuário for saída de tensor, converta-o para o tipo NvDsInferTensorMeta.

  5. Obtenha informações sobre a forma de entrada e a camada de saída do modelo.

  6. Converta os dados da camada de saída do tipo C para o array numpy do Python.

  7. Pós-processamento da saída do modelo, incluindo ajuste de dimensões, adição de probabilidades de classes falsas, mapeamento de coordenadas para tamanhos de tela, etc.

  8. O pós-processamento adicional é executado na saída processada, incluindo supressão não máxima, etc.

  9. Se houver resultados de previsão válidos, esses resultados serão adicionados aos metadados do objeto do quadro e exibidos no quadro.

  10. Atualize a taxa de quadros do quadro.

  11. Marca o quadro como tendo sido inferido.

Os passos gerais para implementar esta função são os seguintes:

  1. Obtenha o buffer do GStreamer e obtenha metadados em lote dele. Esta etapa usa a função gst_buffer_get_nvds_batch_meta() para obter metadados em lote.

  2. Atravesse os metadados de cada quadro. Esta etapa pode ser executada em C++ usando iteradores ou loops padrão.

  3. Para cada quadro, itere sobre os metadados do usuário. Esta etapa pode ser executada em C++ usando iteradores ou loops padrão.

  4. Se o tipo de metadados do usuário for saída de tensor, converta-o para o tipo NvDsInferTensorMeta. Esta etapa usa NvDsInferNetworkInfo e NvDsInferLayerInfo para obter essas informações.

  5. Obtenha informações sobre a forma de entrada e a camada de saída do modelo. Esta etapa pode ser realizada em C++ usando a API DeepStream.

  6. Converta os dados da camada de saída do tipo C para array ou vetor C++. Esta etapa pode ser realizada em C++ usando matrizes ou vetores padrão.

  7. Pós-processamento da saída do modelo, incluindo ajuste de dimensões, adição de probabilidades de classes falsas, mapeamento de coordenadas para tamanhos de tela, etc. Você pode usar a função nvds_add_display_meta_to_frame() para adicionar metadados de exibição ao quadro.

  8. O pós-processamento adicional é executado na saída processada, incluindo supressão não máxima, etc. Esta etapa pode precisar usar ou implementar o algoritmo correspondente em C++.

  9. Se houver resultados de previsão válidos, esses resultados serão adicionados aos metadados do objeto do quadro e exibidos no quadro. Esta etapa pode ser realizada em C++ usando a API DeepStream.

  10. Atualize a taxa de quadros do quadro. Nesta etapa, frame_meta->bInferDone pode ser definido como true para marcar que o quadro foi inferido.

  11. Marca o quadro como tendo sido inferido. Esta etapa pode ser realizada em C++ usando a API DeepStream.

Acima estão as etapas gerais para converter esta função Python em C++. A implementação do código específico pode variar de acordo com suas necessidades e ambiente específicos.

4. Implemente esta função de callback passo a passo

4.1 Obtenha o buffer do GStreamer

static GstPadProbeReturn pose_src_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer u_data)
{
    
    
    g_print("pose_src_pad_buffer_probe called\n");

    // 获取GstBuffer
    GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER(info);
    if (!buf) {
    
    
        g_print("Unable to get GstBuffer\n");
        return GST_PAD_PROBE_OK;
    }

    // 获取batch metadata
    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);
    if (!batch_meta) {
    
    
        g_print("Unable to get batch metadata\n");
        return GST_PAD_PROBE_OK;
    }

    // 打印一些信息
    g_print("Successfully got GstBuffer and batch metadata\n");
    g_print("Batch meta frame count: %d\n", batch_meta->num_frames_in_batch);

    return GST_PAD_PROBE_OK;
}

4.2 遍历: batch metadata -> frame_meta_list -> user metadata

static GstPadProbeReturn pose_src_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer u_data)
{
    
    
    g_print("pose_src_pad_buffer_probe called\n");

    // 获取GstBuffer
    GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER(info);
    if (!buf) {
    
    
        g_print("Unable to get GstBuffer\n");
        return GST_PAD_PROBE_OK;
    }

    // 获取batch metadata
    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);
    if (!batch_meta) {
    
    
        g_print("Unable to get batch metadata\n");
        return GST_PAD_PROBE_OK;
    }

    // 遍历每一帧的元数据
    for (NvDsMetaList *l_frame = batch_meta->frame_meta_list; l_frame != NULL; l_frame = l_frame->next) {
    
    
        NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);

        // 对于每一帧,遍历其用户metadata
        for (NvDsMetaList *l_user = frame_meta->frame_user_meta_list; l_user != NULL; l_user = l_user->next) {
    
    
            NvDsUserMeta *user_meta = (NvDsUserMeta *)(l_user->data);
            g_print("Successfully got user metadata\n");
            g_print("User metadata type: %d\n", user_meta->base_meta.meta_type);
        }
    }

    return GST_PAD_PROBE_OK;
}
User metadata type: 12

Isso faz parte da definição da enumeração NvDsMetaType:

typedef enum
{
  NVDS_META_INVALID = 0,
  NVDS_META_FRAME_INFO,
  NVDS_META_EVENT_MSG,
  NVDS_META_STREAM_INFO,
  NVDS_META_SOURCE_INFO,
  NVDS_META_USER,
  NVDS_META_RESERVED_1,
  NVDS_META_RESERVED_2,
  NVDS_META_RESERVED_3,
  NVDS_META_RESERVED_4,
  NVDS_META_RESERVED_5,
  NVDS_META_RESERVED_6,
  NVDSINFER_TENSOR_OUTPUT_META = 12,
  /* More types */
} NvDsMetaType;

Isso significa que esses metadados do usuário são metadados de saída do tensor de inferência, que contêm os resultados da inferência do modelo

4.3 Retire esses dados

Ao usar isso, há uma explicação do que é o arquivo de cabeçalho correspondente e também há comentários no meu código
https://docs.nvidia.com/metropolis/deepstream/4.0/dev-guide/DeepStream_Development_Guide/baggage/structNvDsInferTensorMeta. html

static GstPadProbeReturn pose_src_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer u_data)
{
    
    
    g_print("pose_src_pad_buffer_probe called\n");

    // 获取GstBuffer
    GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER(info);
    if (!buf) {
    
    
        g_print("Unable to get GstBuffer\n");
        return GST_PAD_PROBE_OK;
    }

    // 获取batch metadata
    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);
    if (!batch_meta) {
    
    
        g_print("Unable to get batch metadata\n");
        return GST_PAD_PROBE_OK;
    }

    // 遍历每一帧的元数据
    for (NvDsMetaList *l_frame = batch_meta->frame_meta_list; l_frame != NULL; l_frame = l_frame->next) {
    
    
        NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);

        // 对于每一帧,遍历其用户metadata
        for (NvDsMetaList *l_user = frame_meta->frame_user_meta_list; l_user != NULL; l_user = l_user->next) 
        {
    
    
            NvDsUserMeta *user_meta = (NvDsUserMeta *)(l_user->data);
            
            // 如果用户metadata的类型是tensor output,那么将其转换为NvDsInferTensorMeta类型
            if (user_meta->base_meta.meta_type == 12) {
    
    
                NvDsInferTensorMeta *tensor_meta = (NvDsInferTensorMeta *)(user_meta->user_meta_data);
                g_print("Successfully casted user metadata to tensor metadata\n");
            }
        }
    }

    return GST_PAD_PROBE_OK;
}

4.4 Use este Tensor_Meta alterado para obter a entrada e a saída do modelo

Esta etapa é feita para garantir que os dados sejam lidos corretamente, porque este projeto é feito em Yolov8-pose, a entrada é 3x640x640 e a saída é 56x8400

56 = bbox(4) + confiança(1) + pontos-chave(3 x 17) = 4 + 1 + 0 + 51 = 56

Se yolov7-pose for usado aqui, a saída será 57

bbox(4) + confiança(1) + cls(1) + pontos-chave(3 x 17) = 4 + 1 + 1 + 51 = 57

static GstPadProbeReturn pose_src_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer u_data)
{
    
    
    g_print("pose_src_pad_buffer_probe called\n");

    // 获取GstBuffer
    GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER(info);
    if (!buf) {
    
    
        g_print("Unable to get GstBuffer\n");
        return GST_PAD_PROBE_OK;
    }

    // 获取batch metadata
    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);
    if (!batch_meta) {
    
    
        g_print("Unable to get batch metadata\n");
        return GST_PAD_PROBE_OK;
    }

    // 遍历每一帧的元数据
    for (NvDsMetaList *l_frame = batch_meta->frame_meta_list; l_frame != NULL; l_frame = l_frame->next) {
    
    
        NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);

        // 对于每一帧,遍历其用户metadata
        for (NvDsMetaList *l_user = frame_meta->frame_user_meta_list; l_user != NULL; l_user = l_user->next) 
        {
    
    
            NvDsUserMeta *user_meta = (NvDsUserMeta *)(l_user->data);
            
            // 如果用户metadata的类型是tensor output,那么将其转换为NvDsInferTensorMeta类型
            if (user_meta->base_meta.meta_type == 12) {
    
    
                NvDsInferTensorMeta *tensor_meta = (NvDsInferTensorMeta *)(user_meta->user_meta_data);

                // 获取模型的输入形状
                NvDsInferNetworkInfo network_info = tensor_meta->network_info;
                g_print("Model input shape: %d x %d x %d\n", network_info.channels, network_info.height, network_info.width);

                // 获取模型的输出层信息
                for (unsigned int i = 0; i < tensor_meta->num_output_layers; i++) {
    
    
                    NvDsInferLayerInfo output_layer_info = tensor_meta->output_layers_info[i];
                    NvDsInferDims dims = output_layer_info.inferDims;
                    g_print("Output layer %d: %s, dimensions: ", i, output_layer_info.layerName);
                    for (int j = 0; j < dims.numDims; j++) {
    
    
                        g_print("%d ", dims.d[j]);
                    }
                    g_print("\n");
                }

            }
        }
    }

    return GST_PAD_PROBE_OK;
}

Alinhar com os resultados do raciocínio do TensorRT

INFO: [Implicit Engine Info]: layers num: 2
0   INPUT  kFLOAT images          3x640x640       
1   OUTPUT kFLOAT output0         56x8400  

Aqui está o que imprimimos

Model input shape: 3 x 640 x 640
Output layer 0: output0, dimensions: 56 8400

Acho que você gosta

Origin blog.csdn.net/bobchen1017/article/details/131669184
Recomendado
Clasificación