検出器を追加して、YOLOv8 を実際の戦闘に展開しましょう!

検出器を追加して、YOLOv8 を実際の戦闘に展開しましょう!

この記事は、Heart of Autonomous Driving の公開アカウントから許可を得て転載したものです。転載については出典元にお問い合わせください。

0 検出器を追加する

この記事は、Han Bo の「CUDA および TensorRT デプロイメント実践コース」の第 6 章を学習中の個人的な学習ノートです。どなたでも議論したり一緒に勉強したりすることができます。

1 onnxをエクスポートする際の注意点

pip install ultralytics ではなく、git clone を選択して yolov8 をインストールします。

 # Clone the ultralytics repository git clone https://github.com/ultralytics/ultralytics # Navigate to the cloned directory cd ultralytics # Install the package in editable mode for development pip install -e .

次にonnxをエクスポートして見てみましょう

model = YOLO('yolov8n.pt') # load an official model model = YOLO('path/to/best.pt') # load a custom trained model # Export the model model.export(format='onnx')

写真

この onnx はこのように見えますが、メモリが連続しているため、速度を上げるには、この出力を 1x8400x84 に変更する必要があります。この方法では、データは 8400 個の bbox 情報 (cx、cy、w、h) が一緒に接続されているためです。そうでない場合、公式情報によると、ここでは 8400 個の cx ごとに、次に 8400 個の cy ごとにこのように配置されているため、ここで転置する必要があります。簡単なコードは次のとおりです。

 # ultralytics/ultralytics/nn/modules/head.py class Detect(nn.Module): # ... def forward(self, x): # ... y = torch.cat((dbox, cls.sigmoid()), 1) y = y.transpose(1, 2) return y if self.export else (y, x)

ここで転置を加えると、次のようになります

写真

このようにして、yolov8のonnxは正常にエクスポートされます

2. 分類フレームワークで何が変更されたかを確認する

ここで、この推論フレームワークの動作を確認してみましょう。

まず、Worker は、モデルの初期化、画像の読み込み、推論の実行など、モデルのライフサイクルの管理を担当するクラスです。タスクの種類 (分類または検出) に基づいて、対応するモデル インスタンスを作成します。

次に、分類器と検出器の 2 種類のモデルがあります。どちらのモデルも Model 基本クラスから継承します。

分類器は画像分類タスク用のモデルです。前処理や後処理など、分類タスクに特有のいくつかのメソッドが含まれています。

Detector は、オブジェクト検出タスク用のモデルです。また、前処理や後処理などの検出タスク固有の方法もいくつか含まれています。

どちらのモデルにも、推論エンジンの作成、入出力バインディングの設定、メモリの割り当てなど、モデルを初期化するための独自のセットアップ メソッドがあります。さらに、入力画像を処理して結果を出力するための独自の前処理および後処理メソッドも備えています。

写真

主に以下のファイルを変更します

  • src/cpp/trt_detector.cpp
  • trt_worker のソースコード
  • src/cpp/trt_model.cpp
  • trt_detector.hpp をインクルードします
  • inlcude/trt_worker.hpp

以下はフレームワーク全体のフローチャートです

写真

実際、上記のフローチャートから、前処理と後処理を除いて、他のすべてが似ていることが簡単にわかります。そのため、ワーカーは m_detector といくつかの bbox データ構造を追加します。

ここでは処理前と処理後に焦点を当てます

3 前処理

画像を読み取り、前処理関数を呼び出します。ここでは、前処理の GPU バージョンを例に挙げます。以下は Detector クラスで実装されています。最初に画像を読み取り、次に param から情報を読み取り、ここで呼び出します。

 bool Detector::preprocess_gpu() { /*Preprocess -- yolo的预处理并没有mean和std,所以可以直接skip掉mean和std的计算*/ /*Preprocess -- 读取数据*/ m_inputImage = cv::imread(m_imagePath); if (m_inputImage.data == nullptr) { LOGE("ERROR: file not founded! Program terminated"); return false; } /*Preprocess -- 测速*/ m_timer->start_gpu(); /*Preprocess -- 使用GPU进行warpAffine, 并将结果返回到m_inputMemory中*/ preprocess::preprocess_resize_gpu(m_inputImage, m_inputMemory[1], m_params->img.h, m_params->img.w, preprocess::tactics::GPU_WARP_AFFINE); m_timer->stop_gpu(); m_timer->duration_gpu("preprocess(GPU)"); return true; }

これは通常のカーネル関数ステップであり、メモリを割り当ててホストからデバイスにデータを移動し、cu ファイル内の関数の実行を開始し、cu ファイル内の関数を実行してカーネル関数を呼び出します。

 void preprocess_resize_gpu( cv::Mat &h_src, float* d_tar, const int& tar_h, const int& tar_w, tactics tac) { uint8_t* d_src = nullptr; int height = h_src.rows; int width = h_src.cols; int chan = 3; int src_size = height * width * chan * sizeof(uint8_t); int norm_size = 3 * sizeof(float); // 分配device上的src的内存CUDA_CHECK(cudaMalloc(&d_src, src_size)); // 将数据拷贝到device上CUDA_CHECK(cudaMemcpy(d_src, h_src.data, src_size, cudaMemcpyHostToDevice)); // device上处理resize, BGR2RGB的核函数resize_bilinear_gpu(d_tar, d_src, tar_w, tar_h, width, height, tac); // host和device进行同步处理CUDA_CHECK(cudaDeviceSynchronize()); CUDA_CHECK(cudaFree(d_src)); // 因为接下来会继续在gpu上进行处理,所以这里不用把结果返回到host }

これは cu のカーネル関数です。主にカーネル関数を呼び出すために使用されます。カーネル関数には多くの種類があります。メソッドを選択することで、さまざまなカーネル関数を使用できます。

 warpaffine_init(srcH, srcW, tarH, tarW); warpaffine_BGR2RGB_kernel <<<dimGrid, dimBlock>>> (d_tar, d_src, trans, affine_matrix);
 void resize_bilinear_gpu( float* d_tar, uint8_t* d_src, int tarW, int tarH, int srcW, int srcH, tactics tac) { dim3 dimBlock(32, 32, 1); dim3 dimGrid(tarW / 32 + 1, tarH / 32 + 1, 1); //scaled resize float scaled_h = (float)srcH / tarH; float scaled_w = (float)srcW / tarW; float scale = (scaled_h > scaled_w ? scaled_h : scaled_w); switch (tac) { case tactics::GPU_NEAREST: nearest_BGR2RGB_nhwc2nchw_kernel <<<dimGrid, dimBlock>>> (d_tar, d_src, tarW, tarH, srcW, srcH, scaled_w, scaled_h); break; case tactics::GPU_NEAREST_CENTER: nearest_BGR2RGB_nhwc2nchw_kernel <<<dimGrid, dimBlock>>> (d_tar, d_src, tarW, tarH, srcW, srcH, scale, scale); break; case tactics::GPU_BILINEAR: bilinear_BGR2RGB_nhwc2nchw_kernel <<<dimGrid, dimBlock>>> (d_tar, d_src, tarW, tarH, srcW, srcH, scaled_w, scaled_h); break; case tactics::GPU_BILINEAR_CENTER: bilinear_BGR2RGB_nhwc2nchw_shift_kernel <<<dimGrid, dimBlock>>> (d_tar, d_src, tarW, tarH, srcW, srcH, scale, scale); break; case tactics::GPU_WARP_AFFINE: warpaffine_init(srcH, srcW, tarH, tarW); warpaffine_BGR2RGB_kernel <<<dimGrid, dimBlock>>> (d_tar, d_src, trans, affine_matrix); break; default: LOGE("ERROR: Wrong GPU resize tactics selected. Program terminated"); exit(1); } }

これらの機能の説明は次のとおりです。

1.warpaffine_init:

 void warpaffine_init(int srcH, int srcW, int tarH, int tarW){ trans.src_h = srcH; trans.src_w = srcW; trans.tar_h = tarH; trans.tar_w = tarW; affine_matrix.init(trans); }

アフィン変換に必要なパラメータを初期化します。ソース画像と宛先画像の高さと幅を設定し、AffineMatrix オブジェクトを初期化します。

2.アフィン変換:

これは、アフィン変換を実行するために CPU と GPU の両方で呼び出すことができる関数です。渡された変換行列とソース座標に基づいて、宛先座標を計算します。

 __host__ __device__ void affine_transformation( float trans_matrix[6], int src_x, int src_y, float* tar_x, float* tar_y) { *tar_x = trans_matrix[0] * src_x + trans_matrix[1] * src_y + trans_matrix[2]; *tar_y = trans_matrix[3] * src_x + trans_matrix[4] * src_y + trans_matrix[5]; }

3.nearest_BGR2RGB_nhwc2nchw_norm_kernel:

これは、画像を BGR 形式から RGB 形式に変換し、画像を NHWC 形式から NCHW 形式に変換し、スケーリングに最近傍補間法を適用する CUDA カーネル関数です。また、ピクセル値の正規化(平均を減算し、標準偏差で割る)も含まれます。

 __global__ void nearest_BGR2RGB_nhwc2nchw_norm_kernel( float* tar, uint8_t* src, int tarW, int tarH, int srcW, int srcH, float scaled_w, float scaled_h, float* d_mean, float* d_std) { // nearest neighbour -- resized之后的图tar上的坐标int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; // nearest neighbour -- 计算最近坐标int src_y = floor((float)y * scaled_h); int src_x = floor((float)x * scaled_w); if (src_x < 0 || src_y < 0 || src_x > srcW || src_y > srcH) { // nearest neighbour -- 对于越界的部分,不进行计算} else { // nearest neighbour -- 计算tar中对应坐标的索引int tarIdx = y * tarW + x; int tarArea = tarW * tarH; // nearest neighbour -- 计算src中最近邻坐标的索引int srcIdx = (src_y * srcW + src_x) * 3; // nearest neighbour -- 实现nearest beighbour的resize + BGR2RGB + nhwc2nchw + norm tar[tarIdx + tarArea * 0] = (src[srcIdx + 2] / 255.0f - d_mean[2]) / d_std[2]; tar[tarIdx + tarArea * 1] = (src[srcIdx + 1] / 255.0f - d_mean[1]) / d_std[1]; tar[tarIdx + tarArea * 2] = (src[srcIdx + 0] / 255.0f - d_mean[0]) / d_std[0]; } }

4.bilinear_BGR2RGB_nhwc2nchw_norm_カーネル:

前の関数と似ていますが、スケーリングには双線形補間を使用します。また、BGR から RGB へのカラー変換、NHWC か​​ら NCHW への形式変換、ピクセル値の正規化も実行します。

 __global__ void bilinear_BGR2RGB_nhwc2nchw_norm_kernel( float* tar, uint8_t* src, int tarW, int tarH, int srcW, int srcH, float scaled_w, float scaled_h, float* d_mean, float* d_std) { // bilinear interpolation -- resized之后的图tar上的坐标int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; // // bilinear interpolation -- 计算x,y映射到原图时最近的4个坐标int src_y1 = floor((y + 0.5) * scaled_h - 0.5); int src_x1 = floor((x + 0.5) * scaled_w - 0.5); int src_y2 = src_y1 + 1; int src_x2 = src_x1 + 1; if (src_y1 < 0 || src_x1 < 0 || src_y2 > srcH || src_x2 > srcW) { // bilinear interpolation -- 对于越界的坐标不进行计算} else { // bilinear interpolation -- 计算原图上的坐标(浮点类型)在0~1之间的值float th = ((y + 0.5) * scaled_h - 0.5) - src_y1; float tw = ((x + 0.5) * scaled_w - 0.5) - src_x1; // bilinear interpolation -- 计算面积(这里建议自己手画一张图来理解一下) float a1_1 = (1.0 - tw) * (1.0 - th); //右下float a1_2 = tw * (1.0 - th); //左下float a2_1 = (1.0 - tw) * th; //右上float a2_2 = tw * th; //左上// bilinear interpolation -- 计算4个坐标所对应的索引int srcIdx1_1 = (src_y1 * srcW + src_x1) * 3; //左上int srcIdx1_2 = (src_y1 * srcW + src_x2) * 3; //右上int srcIdx2_1 = (src_y2 * srcW + src_x1) * 3; //左下int srcIdx2_2 = (src_y2 * srcW + src_x2) * 3; //右下// bilinear interpolation -- 计算resized之后的图的索引int tarIdx = y * tarW + x; int tarArea = tarW * tarH; // bilinear interpolation -- 实现bilinear interpolation的resize + BGR2RGB + NHWC2NCHW normalization // 注意,这里tar和src进行遍历的方式是不一样的tar[tarIdx + tarArea * 0] = (round((a1_1 * src[srcIdx1_1 + 2] + a1_2 * src[srcIdx1_2 + 2] + a2_1 * src[srcIdx2_1 + 2] + a2_2 * src[srcIdx2_2 + 2])) / 255.0f - d_mean[2]) / d_std[2]; tar[tarIdx + tarArea * 1] = (round((a1_1 * src[srcIdx1_1 + 1] + a1_2 * src[srcIdx1_2 + 1] + a2_1 * src[srcIdx2_1 + 1] + a2_2 * src[srcIdx2_2 + 1])) / 255.0f - d_mean[1]) / d_std[1]; tar[tarIdx + tarArea * 2] = (round((a1_1 * src[srcIdx1_1 + 0] + a1_2 * src[srcIdx1_2 + 0] + a2_1 * src[srcIdx2_1 + 0] + a2_2 * src[srcIdx2_2 + 0])) / 255.0f - d_mean[0]) / d_std[0]; } }

5.bilinear_BGR2RGB_nhwc2nchw_shift_norm_kernel:

この関数は、双線形補間スケーリング、BGR から RGB へのカラー変換、および形式変換も実行しますが、スケーリング中の座標シフトも考慮し、ピクセル値の正規化も実行します。

 __global__ void bilinear_BGR2RGB_nhwc2nchw_shift_norm_kernel( float* tar, uint8_t* src, int tarW, int tarH, int srcW, int srcH, float scaled_w, float scaled_h, float* d_mean, float* d_std) { // resized之后的图tar上的坐标int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; // bilinear interpolation -- 计算x,y映射到原图时最近的4个坐标int src_y1 = floor((y + 0.5) * scaled_h - 0.5); int src_x1 = floor((x + 0.5) * scaled_w - 0.5); int src_y2 = src_y1 + 1; int src_x2 = src_x1 + 1; if (src_y1 < 0 || src_x1 < 0 || src_y2 > srcH || src_x2 > srcW) { // bilinear interpolation -- 对于越界的坐标不进行计算} else { // bilinear interpolation -- 计算原图上的坐标(浮点类型)在0~1之间的值float th = (float)y * scaled_h - src_y1; float tw = (float)x * scaled_w - src_x1; // bilinear interpolation -- 计算面积(这里建议自己手画一张图来理解一下) float a1_1 = (1.0 - tw) * (1.0 - th); // 右下float a1_2 = tw * (1.0 - th); // 左下float a2_1 = (1.0 - tw) * th; // 右上float a2_2 = tw * th; // 左上// bilinear interpolation -- 计算4个坐标所对应的索引int srcIdx1_1 = (src_y1 * srcW + src_x1) * 3; // 左上int srcIdx1_2 = (src_y1 * srcW + src_x2) * 3; // 右上int srcIdx2_1 = (src_y2 * srcW + src_x1) * 3; // 左下int srcIdx2_2 = (src_y2 * srcW + src_x2) * 3; // 右下// bilinear interpolation -- 计算原图在目标图中的x, y方向上的偏移量y = y - int(srcH / (scaled_h * 2)) + int(tarH / 2); x = x - int(srcW / (scaled_w * 2)) + int(tarW / 2); // bilinear interpolation -- 计算resized之后的图的索引int tarIdx = (y * tarW + x) * 3; int tarArea = tarW * tarH; // bilinear interpolation -- 实现bilinear interpolation + BGR2RGB + shift + nhwc2nchw tar[tarIdx + tarArea * 0] = (round((a1_1 * src[srcIdx1_1 + 2] + a1_2 * src[srcIdx1_2 + 2] + a2_1 * src[srcIdx2_1 + 2] + a2_2 * src[srcIdx2_2 + 2])) / 255.0f - d_mean[2]) / d_std[2]; tar[tarIdx + tarArea * 1] = (round((a1_1 * src[srcIdx1_1 + 1] + a1_2 * src[srcIdx1_2 + 1] + a2_1 * src[srcIdx2_1 + 1] + a2_2 * src[srcIdx2_2 + 1])) / 255.0f - d_mean[1]) / d_std[1]; tar[tarIdx + tarArea * 2] = (round((a1_1 * src[srcIdx1_1 + 0] + a1_2 * src[srcIdx1_2 + 0] + a2_1 * src[srcIdx2_1 + 0] + a2_2 * src[srcIdx2_2 + 0])) / 255.0f - d_mean[0]) / d_std[0]; } }

6.nearest_BGR2RGB_nhwc2nchw_kernel と bilinear_BGR2RGB_nhwc2nchw_kernel:

これら 2 つの関数は、前述の関数の _norm バージョンに似ていますが、ピクセルの正規化は実行しません。

 __global__ void nearest_BGR2RGB_nhwc2nchw_kernel( float* tar, uint8_t* src, int tarW, int tarH, int srcW, int srcH, float scaled_w, float scaled_h) { // nearest neighbour -- resized之后的图tar上的坐标int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; // nearest neighbour -- 计算最近坐标int src_y = floor((float)y * scaled_h); int src_x = floor((float)x * scaled_w); if (src_x < 0 || src_y < 0 || src_x > srcW || src_y > srcH) { // nearest neighbour -- 对于越界的部分,不进行计算} else { // nearest neighbour -- 计算tar中对应坐标的索引int tarIdx = y * tarW + x; int tarArea = tarW * tarH; // nearest neighbour -- 计算src中最近邻坐标的索引int srcIdx = (src_y * srcW + src_x) * 3; // nearest neighbour -- 实现nearest beighbour的resize + BGR2RGB + nhwc2nchw + norm tar[tarIdx + tarArea * 0] = src[srcIdx + 2] / 255.0f; tar[tarIdx + tarArea * 1] = src[srcIdx + 1] / 255.0f; tar[tarIdx + tarArea * 2] = src[srcIdx + 0] / 255.0f; } }

7.bilinear_BGR2RGB_nhwc2nchw_shift_kernel:

bilinear_BGR2RGB_nhwc2nchw_kernel に似ていますが、座標シフトを考慮します。

 __global__ void bilinear_BGR2RGB_nhwc2nchw_shift_kernel( float* tar, uint8_t* src, int tarW, int tarH, int srcW, int srcH, float scaled_w, float scaled_h) { // resized之后的图tar上的坐标int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; // bilinear interpolation -- 计算x,y映射到原图时最近的4个坐标int src_y1 = floor((y + 0.5) * scaled_h - 0.5); int src_x1 = floor((x + 0.5) * scaled_w - 0.5); int src_y2 = src_y1 + 1; int src_x2 = src_x1 + 1; if (src_y1 < 0 || src_x1 < 0 || src_y2 > srcH || src_x2 > srcW) { // bilinear interpolation -- 对于越界的坐标不进行计算} else { // bilinear interpolation -- 计算原图上的坐标(浮点类型)在0~1之间的值float th = (float)y * scaled_h - src_y1; float tw = (float)x * scaled_w - src_x1; // bilinear interpolation -- 计算面积(这里建议自己手画一张图来理解一下) float a1_1 = (1.0 - tw) * (1.0 - th); // 右下float a1_2 = tw * (1.0 - th); // 左下float a2_1 = (1.0 - tw) * th; // 右上float a2_2 = tw * th; // 左上// bilinear interpolation -- 计算4个坐标所对应的索引int srcIdx1_1 = (src_y1 * srcW + src_x1) * 3; // 左上int srcIdx1_2 = (src_y1 * srcW + src_x2) * 3; // 右上int srcIdx2_1 = (src_y2 * srcW + src_x1) * 3; // 左下int srcIdx2_2 = (src_y2 * srcW + src_x2) * 3; // 右下// bilinear interpolation -- 计算原图在目标图中的x, y方向上的偏移量y = y - int(srcH / (scaled_h * 2)) + int(tarH / 2); x = x - int(srcW / (scaled_w * 2)) + int(tarW / 2); // bilinear interpolation -- 计算resized之后的图的索引int tarIdx = y * tarW + x; int tarArea = tarW * tarH; // bilinear interpolation -- 实现bilinear interpolation + BGR2RGB + shift + nhwc2nchw tar[tarIdx + tarArea * 0] = round((a1_1 * src[srcIdx1_1 + 2] + a1_2 * src[srcIdx1_2 + 2] + a2_1 * src[srcIdx2_1 + 2] + a2_2 * src[srcIdx2_2 + 2])) / 255.0f; tar[tarIdx + tarArea * 1] = round((a1_1 * src[srcIdx1_1 + 1] + a1_2 * src[srcIdx1_2 + 1] + a2_1 * src[srcIdx2_1 + 1] + a2_2 * src[srcIdx2_2 + 1])) / 255.0f; tar[tarIdx + tarArea * 2] = round((a1_1 * src[srcIdx1_1 + 0] + a1_2 * src[srcIdx1_2 + 0] + a2_1 * src[srcIdx2_1 + 0] + a2_2 * src[srcIdx2_2 + 0])) / 255.0f; } }

8.warpaffine_BGR2RGB_カーネル:

アフィン変換を実行し、画像を BGR 形式から RGB 形式に変換します。

 __global__ void warpaffine_BGR2RGB_kernel( float* tar, uint8_t* src, TransInfo trans, AffineMatrix affine_matrix) { float src_x, src_y; int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; affine_transformation(affine_matrix.reverse, x + 0.5, y + 0.5, &src_x, &src_y); int src_x1 = floor(src_x - 0.5); int src_y1 = floor(src_y - 0.5); int src_x2 = src_x1 + 1; int src_y2 = src_y1 + 1; if (src_y1 < 0 || src_x1 < 0 || src_y1 > trans.src_h || src_x1 > trans.src_w) { } else { float tw = src_x - src_x1; float th = src_y - src_y1; float a1_1 = (1.0 - tw) * (1.0 - th); float a1_2 = tw * (1.0 - th); float a2_1 = (1.0 - tw) * th; float a2_2 = tw * th; int srcIdx1_1 = (src_y1 * trans.src_w + src_x1) * 3; int srcIdx1_2 = (src_y1 * trans.src_w + src_x2) * 3; int srcIdx2_1 = (src_y2 * trans.src_w + src_x1) * 3; int srcIdx2_2 = (src_y2 * trans.src_w + src_x2) * 3; int tarIdx = y * trans.tar_w + x; int tarArea = trans.tar_w * trans.tar_h; tar[tarIdx + tarArea * 0] = round((a1_1 * src[srcIdx1_1 + 2] + a1_2 * src[srcIdx1_2 + 2] + a2_1 * src[srcIdx2_1 + 2] + a2_2 * src[srcIdx2_2 + 2])) / 255.0f; tar[tarIdx + tarArea * 1] = round((a1_1 * src[srcIdx1_1 + 1] + a1_2 * src[srcIdx1_2 + 1] + a2_1 * src[srcIdx2_1 + 1] + a2_2 * src[srcIdx2_2 + 1])) / 255.0f; tar[tarIdx + tarArea * 2] = round((a1_1 * src[srcIdx1_1 + 0] + a1_2 * src[srcIdx1_2 + 0] + a2_1 * src[srcIdx2_1 + 0] + a2_2 * src[srcIdx2_2 + 0])) / 255.0f; } }

9. resize_bilinear_gpu (オーバーロードされた 2 つのバージョン):

これら 2 つの関数は CUDA カーネル関数をカプセル化したもので、GPU 上で画像のスケーリング操作を実行するために使用されます。 CUDA スレッド ブロックとグリッド サイズを設定し、スケーリングを計算し、指定された戦略 (最近傍法、双線形補間など) に基づいて画像処理に適したカーネル関数を選択します。 1 つのバージョンには、ピクセル値を正規化する手順も含まれています。 基本的に、warpffineを除いて、これらはすべて分類ネットワークの前処理です。実験にはさまざまなものが使用できます。

4. 後処理

ポイントは次の書き方です。ベクターのようなデータ構造を削除すると、ベクター全体に影響が及びます。そこで、bbox にフラグ モードを与えて、最終的に final_bboxes に追加されるかどうかをチェックできるようにします。これは非常に効率的です。

 vector<bbox> final_bboxes; final_bboxes.reserve(m_bboxes.size()); std::sort(m_bboxes.begin(), m_bboxes.end(), [](bbox& box1, bbox& box2){return box1.confidence > box2.confidence;}); /* * nms在网上有很多实现方法,其中有一些是根据nms的值来动态改变final_bboex的大小(resize, erease) * 这里需要注意的是,频繁的对vector的大小的更改的空间复杂度会比较大,所以尽量不要这么做* 可以通过给bbox设置skip计算的flg来调整。 */ for(int i = 0; i < m_bboxes.size(); i ++){ if (m_bboxes[i].flg_remove) continue; final_bboxes.emplace_back(m_bboxes[i]); for (int j = i + 1; j < m_bboxes.size(); j ++) { if (m_bboxes[j].flg_remove) continue; if (m_bboxes[i].label == m_bboxes[j].label){ if (iou_calc(m_bboxes[i], m_bboxes[j]) > nms_threshold) m_bboxes[j].flg_remove = true; } } } LOGD("the count of bbox after NMS is %d", final_bboxes.size());

5. その後の改良

ここでの主な改善案は、量子化後の精度低下の問題を解決することです。これはマルチタスク タスクであるため、Han Jun はここでいくつかの検査ソリューションを示しました。

  • int8 量子化は入力/出力の近くで行われますか?
  • マルチタスクの場合、すべてのタスクが深刻にダウンしていますか?
  • キャリブレーションデータセットが適切に選択されていませんか?
  • キャリブレーションバッチサイズが適切に選択されていませんか?
  • キャリブレーターが正しく選択されていませんか?
  • 一部の計算は量子化すべきではないでしょうか?
  • ポリグラフを用いた分析

オリジナルリンク: https://mp.weixin.qq.com/s/n9DGA0Wmk1MTiFggoW2e4Q

<<: 

>>: 

ブログ    

推薦する

Weibo の背後にあるビッグデータの原理を探る: 推奨アルゴリズム

推薦システムは早くから誕生していたが、本格的に注目されるようになったのは、「Facebook」に代表...

企業がビッグデータの可能性を最大限に引き出す方法

専門家は、2025 年までにデータ ユニバース、つまりデータ ユニバースの規模が 180 ゼタバイト...

...

畳み込みニューラルネットワークの基礎を1つの記事で学びます。

今日は畳み込みニューラル ネットワークについてお話します。畳み込みニューラル ネットワークは、主に、...

人工知能が学習と発達に及ぼす7つの影響

急速に進化する今日のテクノロジー環境において、人工知能 (AI) はあらゆる業界に革命を起こす可能性...

5Gについて知っておくべきことは何ですか?

1G の時代では、電話をかけたり受けたりすることしかできませんでした。 2G 時代は、電話をかけた...

超便利!追加のコードを書かずに依存性注入の5つの原則をマスターする

この概念に初めて遭遇した場合、一瞬理解できないかもしれません。インターネット上のさまざまな説明により...

...

...

...

「中国版ダヴィンチ」ロボットが人気!ブドウの皮を縫うだけでなく、このような創造的な作業もあります

ブドウを縫うことができる DIY ロボットアームを作りますか? [[428703]]最近、有名な「ハ...

...

ゲームにおけるディープラーニングと AI

[[190049]]この記事は、4月27日にBig Data Talk WeChatコミュニティで...

自律型 AI エージェント: 未来の生産性エンジン

翻訳者 | 崔昊レビュー | Chonglouまとめこの記事では、タスクを自ら作成し、優先順位を付け...