Pytorch導出onnx模型,C++轉化為TensorRT并實現推理過程

Pytorch導出onnx模型,C++轉化為TensorRT并實現推理過程

前言

  1. 本文為旨在實現整個Python導出PyTorch模型,C++轉化為TensorRT并實現推理過程過程,只與模型推理,模型部署相關,不涉及模型訓練。
  2. 為突出整個部署過程而非具體模型本身,本文模型就采用最簡單的分類模型,并且直接使用 torchvision.model 中的權重。檢測、分割等其他模型在前后處理部分會有不同,但是模型本身的導出、轉換和推理的過程基本是一致的。
  3. 本文會先用 Pytorch 測試一個分類模型在一張測試圖片上的結果,將其轉換為 onnx 模型,再用 onnxruntime 測試結果,再用 C++ 將其轉換為 TensorRT 模型,再測試推理結果。預期三者測試結果一致,則轉換成功。
  4. 如果想要測試速度,Python 可以使用 time.perf_counter(), C++ 可以使用 std::chrono::high_resolution_clock 。建議多測數據集中的一些圖片,計算推理時間的均值和方差,而不是只測一張圖片。

1 Pytorch模型推理測試導出onnx

這部分我們使用 torchvision 實例化一個簡單的 ResNet50 分類模型,并將其導出為 onnx 模型。在這個過程中,我們還需要使用一張圖片進行推理,并記錄下 Python 模型的輸出,方便我們后面到處 TensoRT 模型并進行推理時進行準確性的驗證。

由于 torchvision 中的 resnet50 分類模型中是沒有進行最后的 softmax 操作的,這里我們為了之后使用方便,自己新建一個類 ResNet50_wSoftmax 將后處理 softmax 添加到模型中一起導出。

這也是 pytorch 導出 onnx 模型的一個推薦的方式,就是將一些必要后處理添加到模型中一起導出,這樣做有兩個優點:

  • 可以直接得到端到端的 onnx/tensorrt 模型,不必在外面再做后處理操作
  • 再之后我們會將 onnx 模型轉換為 tensorrt 模型,在轉換過程中 tensorrt 會對我們的模型進行一些針對特定的 Nvidia GPU 的推理優化,我們將后處理一起合并到 onnx 模型中,可能可以使得一些算子操作再轉換為 tensorrt 的過程中同樣得到優化。

最終代碼如下:

# export_onnx.py
import torch
import torchvision.models as models
import cv2
import numpy as npclass ResNet50_wSoftmax(torch.nn.Module):# 將softmax后處理合并到模型中,一起導出為onnxdef __init__(self):super().__init__()self.base_model = models.resnet50(pretrained=True)self.softmax = torch.nn.Softmax(dim=1)def forward(self, x):y = self.base_model(x)prob = self.softmax(y)return probdef preprocessing(img):# 預處理:BGR->RGB、歸一化/除均值減標準差IMAGENET_MEAN = [0.485, 0.456, 0.406]IMAGENET_STD = [0.229, 0.224, 0.225]img = img[:, :, ::-1]img = cv2.resize(img, (224, 224))img = img / 255.0img = (img - IMAGENET_MEAN) / IMAGENET_STDimg = img.transpose(2, 0, 1).astype(np.float32)tensor_img = torch.from_numpy(img)[None]return tensor_imgif __name__ == '__main__':# model = models.resnet50(pretrained=True)image_path = 'test.jpg'img = cv2.imread(image_path)tensor_img = preprocessing(img)model = ResNet50_wSoftmax()   # 將后處理添加到模型中model.eval()pred = model(tensor_img)[0]max_idx = torch.argmax(pred)print(f"test_image: {image_path}, max_idx: {max_idx}, max_logit: {pred[max_idx].item()}")dummpy_input = torch.zeros(1, 3, 224, 224)  # onnx的導出需要指定一個輸入,這里直接用上面的tenosr_img也可torch.onnx.export(model, dummpy_input, 'resnet50_wSoftmax.onnx',input_names=['image'],output_names=['predict'],opset_version=11,dynamic_axes={'image': {0: 'batch'}, 'predict': {0: 'batch'}}		# 注意這里指定batchsize是動態可變的)

執行結果會輸出:

test_image: test.jpg, max_idx: 971, probability: 0.994541585445404

這些結果我們一會測試 onnx/tensorrt 模型時用于比對轉換是否有誤差。并得到一個 onnx 模型文件:classifier.onnx

2 onnxruntime推理測試

我們將剛剛得到的 classifier.onnx ,用 onnxruntime 來進行推理測試,看結果是否相同。

這里,我們就復用剛才測試 pytorch 模型時的預處理函數,整個 onnxruntime 推理測試代碼如下:

import onnxruntime as ort
import numpy as np
import cv2
from export_onnx import preprocessingimage_path = 'test.jpg'
ort_session = ort.InferenceSession("classifier.onnx") # 創建一個推理sessionimg = cv2.imread(image_path)
input_img = preprocessing(img)[None]pred = ort_session.run(None, { 'image' : input_img } )[0][0]
max_idx = np.argmax(pred)
print(f"test_image: {image_path}, max_idx: {max_idx}, probability: {pred[max_idx]}")

輸出:

test_image: test.jpg, max_idx: 971, probability: 0.994541585445404

可以看到,跟我們 pytorch 模型的測試結果是一致的。

3 C++ onnx模型轉換為tensorrt模型

本部分重度參考自課程:tensorRT從零起步邁向高性能工業級部署(就業導向)

我們進行模型部署推理肯定是追求極致的推理速度,這時再用 Python 來進行轉換和推理就不合適了,接下來我們就轉戰到 C++ 上,將onnx模型轉換為tensorrt模型。

對于大部分深度學習部署的 C/C++ 的初學者而言,環境配置都是個老大難的問題。本身 C/C++ 的包管理就不如 Python 的 pip、conda 等來的直接方便,再加上各種 nvidia driver/cuda/cudnn/cuda-runtime 的各種版本不對齊的問題,包括筆者在內的許多萌新們初期總是會在環境配置遇到許多問題。但是本文關注的重點是整個模型轉換和部署的過程,不可能花大篇幅再去介紹環境配置,將來有機會再單獨寫一篇介紹 Python/C++ 深度學習模型部署時環境配置的問題,這里就直接給出筆者使用的關鍵軟硬件的版本號/型號。

GPU: RTX 3060ti 12GB

OS: ubuntu 18.04

gcc: 7.5

TensorRT: 8.x

CUDA: 11.2

cuDNN: 8.x

頭文件

包含的頭文件:

// tensorrt相關
#include <NvInfer.h>
#include <NvInferRuntime.h>// onnx解析器相關
#include <onnx-tensorrt/NvOnnxParser.h>// cuda_runtime相關
#include <cuda_runtime.h>// 常用頭文件
#include <stdio.h>
#include <math.h>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <memory>
#include <functional>
#include <unistd.h>
#include <chrono>// opencv
#include <opencv2/opencv.hpp>

logger類

首先我們要準備一個 logger 類,來打印構建 tensorrt 模型過程中的一些錯誤或警告。按照指定的嚴重性程度 (severity),來打印信息。

inline const char* severity_string(nvinfer1::ILogger::Severity t) {switch (t) {case nvinfer1::ILogger::Severity::kINTERNAL_ERROR: return "internal_error";case nvinfer1::ILogger::Severity::kERROR: return "error";case nvinfer1::ILogger::Severity::kWARNING: return "warning";case nvinfer1::ILogger::Severity::kINFO: return "info";case nvinfer1::ILogger::Severity::kVERBOSE: return "verbose";default: return "unknown";}
}class TRTLogger : public nvinfer1::ILogger {
public:virtual void log(Severity severity, nvinfer1::AsciiChar const* msg) noexcept override {if (severity <= Severity::kWARNING) {if (severity == Severity::kWARNING) printf("\033[33m%s: %s\033[0m\n", severity_string(severity), msg);else if (severity == Severity::kERROR) printf("\031[33m%s: %s\033[0m\n", severity_string(severity), msg);else printf("%s: %s\n", severity_string(severity), msg);}}
};

build_model函數

build_model 函數,各步驟已在代碼中添加注釋:

bool build_model() {if (isFileExist( "classifier.trtmodel" )) {printf("classifier.trtmodel already exists.\n");return true;}TRTLogger logger;// 下面的builder, config, network是基本需要的組件// 形象的理解是你需要一個builder去build這個網絡,網絡自身有結構,這個結構可以有不同的配置nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);// 創建一個構建配置,指定TensorRT應該如何優化模型,tensorRT生成的模型只能在特定配置下運行nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();// 創建網絡定義,其中createNetworkV2(1)表示采用顯性batch size,新版tensorRT(>=7.0)時,不建議采用0非顯性batch sizenvinfer1::INetworkDefinition* network = builder->createNetworkV2(1);// onnx parser解析器來解析onnx模型auto parser = nvonnxparser::createParser(*network, logger);if (!parser->parseFromFile("classifier.onnx", 1)) {printf("Failed to parse classifier.onnx.\n");return false;}// 設置工作區大小printf("Workspace Size = %.2f MB\n", (1 << 28) / 1024.0f / 1024.0f);config->setMaxWorkspaceSize(1 << 28);// 需要通過profile來使得batchsize時動態可變的,這與我們之前導出onnx指定的動態batchsize是對應的int maxBatchSize = 10;auto profile = builder->createOptimizationProfile();auto input_tensor = network->getInput(0);auto input_dims = input_tensor->getDimensions();// 設置batchsize的最大/最小/最優值input_dims.d[0] = 1;profile->setDimensions(input_tensor->getName(), nvinfer1::OptProfileSelector::kMIN, input_dims);profile->setDimensions(input_tensor->getName(), nvinfer1::OptProfileSelector::kOPT, input_dims);input_dims.d[0] = maxBatchSize;profile->setDimensions(input_tensor->getName(), nvinfer1::OptProfileSelector::kMAX, input_dims);config->addOptimizationProfile(profile);// 開始構建tensorrt模型enginenvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);if (engine == nullptr) {printf("Build engine failed.\n");return false;}// 將構建好的tensorrt模型engine反序列化(保存成文件)nvinfer1::IHostMemory* model_data = engine->serialize();FILE* f = fopen("classifier.trtmodel", "wb");fwrite(model_data->data(), 1, model_data->size(), f);fclose(f);// 逆序destory掉指針model_data->destroy();engine->destroy();network->destroy();config->destroy();builder->destroy();printf("Build Done.\n");return true;
}

調用 build_model 函數成功后,我們會得到一個 classifier.trtmodel 文件。

make_nvshared

上面的實現有個比較不優雅的地方,對于我們創建的 builderconfig 等指針,我們都需要一一進行 destroy,從而避免內存泄漏。實際上,這里我們可以通過共享指針,來實現自動釋放。

shared_ptr<_T> make_nvshared(_T *ptr) {return shared_ptr<_T>(ptr, [](_T* p){p->destroy();});
}

在這里指定一下釋放內存的方式,之后就可以通過類似:

auto network = make_nvshared(builder->createNetworkV2(1));

這樣的方式創建智能指針,他會自己 destroy 釋放,這樣最后幾行 destory 就不用寫了。

4 tensorrt模型推理測試

我們上一步已經成功將 onnx 模型導出為了 tensorrt 模型,現在我們用 tensorrt 模型來進行推理,看一下結果是否與之前 pytorch 和 onnx 推理的結果一致,如果一致,則模型轉換成功。

load_file

load_file 函數用于加載我們的 tensorrt 模型:

vector<unsigned char> load_file(const string& file) {ifstream in(file, ios::in | ios::binary);if (!in.is_open()) return {};in.seekg(0, ios::end);size_t length = in.tellg();vector<uint8_t> data;if (length > 0) {in.seekg(0, ios::beg);data.resize(length);in.read((char*)&data[0], length);}in.close();return data;
}

inference

void inference(const string& image_path) {TRTLogger logger;// 加載模型auto engine_data = load_file("classifier.trtmodel");// 執行推理前,需要創建一個推理的runtime接口實例。與builer一樣,runtime需要loggerauto runtime = make_nvshared(nvinfer1::createInferRuntime(logger));auto engine = make_nvshared(runtime->deserializeCudaEngine(engine_data.data(), engine_data.size()));if (engine == nullptr) {printf("Deserialize cuda engine failed.\n");runtime->destroy();return;}if (engine->getNbBindings() != 2) {printf("Must be single input, single Output, got %d output.\n", engine->getNbBindings() - 1);return;}// 創建CUDA流,以確定這個batch的推理是獨立的cudaStream_t stream = nullptr;checkRuntime(cudaStreamCreate(&stream));auto execution_context = make_nvshared(engine->createExecutionContext());int input_batch = 1;int input_channel = 3;int input_height = 224;int input_width = 224;// 準備好input_data_host和input_data_device,分別表示內存中的數據指針和顯存中的數據指針// 一會兒將預處理過的圖像數據搬運到GPUint input_numel = input_batch * input_channel * input_height * input_width;float* input_data_host = nullptr;float* input_data_device = nullptr;checkRuntime(cudaMallocHost(&input_data_host, input_numel * sizeof(float)));checkRuntime(cudaMalloc(&input_data_device, input_numel * sizeof(float)));// 圖片讀取與預處理,與之前python中的預處理方式一致:// BGR->RGB、歸一化/除均值減標準差float mean[] = {0.406, 0.456, 0.485};float std[] = {0.225, 0.224, 0.229};auto image = cv::imread(image_path);cv::resize(image, image, cv::Size(input_width, input_height));int image_area = image.cols * image.rows;unsigned char* pimage = image.data;float* phost_b = input_data_host + image_area * 0;float* phost_g = input_data_host + image_area * 1;float* phost_r = input_data_host + image_area * 2;for (int i=0; i<image_area; ++i, pimage += 3) {*phost_r++ = (pimage[0] / 255.0f - mean[0]) / std[0];*phost_g++ = (pimage[1] / 255.0f - mean[1]) / std[1];*phost_b++ = (pimage[2] / 255.0f - mean[2]) / std[2];}// 進行推理checkRuntime(cudaMemcpyAsync(input_data_device, input_data_host, input_numel *sizeof(float), cudaMemcpyHostToDevice, stream));const int num_classes = 1000;float output_data_host[num_classes];float* output_data_device = nullptr;checkRuntime(cudaMalloc(&output_data_device, sizeof(output_data_host)));auto input_dims = engine->getBindingDimensions(0);input_dims.d[0] = input_batch;execution_context->setBindingDimensions(0, input_dims);// 用一個指針數組bindings指定input和output在gpu中的指針。float* bindings[] = {input_data_device, output_data_device};bool success = execution_context->enqueueV2((void**)bindings, stream, nullptr);checkRuntime(cudaMemcpyAsync(output_data_host, output_data_device, sizeof(output_data_host), cudaMemcpyDeviceToHost, stream));checkRuntime(cudaStreamSynchronize(stream));float* prob = output_data_host;int predict_label = max_element(prob, prob + num_classes) - prob;float conf = prob[predict_label];printf("test_image: %s, max_idx: %d, probability: %f", image_path.c_str(), predict_label, conf);// 釋放顯存checkRuntime(cudaStreamDestroy(stream));checkRuntime(cudaFreeHost(input_data_host));checkRuntime(cudaFree(input_data_device));checkRuntime(cudaFree(output_data_device));
}

最終得到輸出:

test_image: test.jpg, max_idx: 971, probability: 0.994527

與之前 pytorch 和 onnx 推理的結果基本一致,模型轉換成功。

附錄

給出完整的參考代碼:https://github.com/Adenialzz/Hello-AIDeployment/tree/master/HAID/tensorrt/resnet

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532459.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532459.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532459.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從零Makefile落地算法大項目,完整案例教程

從零Makefile落地算法大項目&#xff0c;完整案例教程 轉自&#xff1a;從零Makefile落地算法大項目&#xff0c;完整案例教程 作者&#xff1a;手寫AI 前言 在這里&#xff0c;你能學到基于Makefile的正式大項目的使用方式和考慮&#xff0c;相信我&#xff0c;其實可以很簡單…

PyTorch擴展自定義PyThonC++(CUDA)算子的若干方法總結

PyTorch擴展自定義PyThon/C(CUDA)算子的若干方法總結 轉自&#xff1a;https://zhuanlan.zhihu.com/p/158643792 作者&#xff1a;奔騰的黑貓 在做畢設的時候需要實現一個PyTorch原生代碼中沒有的并行算子&#xff0c;所以用到了這部分的知識&#xff0c;再不總結就要忘光了 &a…

給 Python 算法插上性能的翅膀——pybind11 落地實踐

給 Python 算法插上性能的翅膀——pybind11 落地實踐 轉自&#xff1a;https://zhuanlan.zhihu.com/p/444805518 作者&#xff1a;jesonxiang&#xff08;向乾彪&#xff09;&#xff0c;騰訊 TEG 后臺開發工程師 1. 背景 目前 AI 算法開發特別是訓練基本都以 Python 為主&…

chrome自動提交文件_收集文檔及提交名單統計

知乎文章若有排版問題請見諒&#xff0c;原文放在個人博客中【歡迎互踩&#xff01;】文叔叔文檔收集使用動機在我們的學習工作中&#xff0c;少不了要讓大家集體提交文件的情況&#xff0c;舉個最簡單的例子&#xff1a;收作業。 傳統的文件收集流程大致是&#xff1a;群內發出…

Pytorch自定義C++/CUDA擴展

Pytorch自定義C/CUDA擴展 翻譯自&#xff1a;官方文檔 PyTorch 提供了大量與神經網絡、張量代數、數據整理和其他操作。但是&#xff0c;我們有時會需要更加定制化的操作。例如&#xff0c;想要使用論文中找到的一種新型的激活函數&#xff0c;或者實現自己設計的算子。 在 Py…

惠普800g1支持什么內存_惠普黑白激光打印機哪種好 惠普黑白激光打印機推薦【圖文詳解】...

打印機的出現讓我們在生活和日常工作中變得越來越方便&#xff0c;不過隨著科技的發展&#xff0c;打印機的類型也變得非常多&#xff0c;其中就有黑白激光打印機&#xff0c;而黑白激光打印機的品牌也有很多&#xff0c;比如我們的惠普黑白激光打印機&#xff0c;今天小編就給…

控制臺輸出顏色控制

控制臺輸出顏色控制 轉自&#xff1a;https://cloud.tencent.com/developer/article/1142372 前端時間&#xff0c;寫了一篇 PHP 在 Console 模式下的進度顯示 &#xff0c;正好最近的一個數據合并項目需要用到控制臺顏色輸出&#xff0c;所以就把相關的信息整理下&#xff0c;…

idea連接跳板機_跳板機服務(jumpserver)

一、跳板機服務作用介紹1、有效管理用戶權限信息2、有效記錄用戶登錄情況3、有效記錄用戶操作行為二、跳板機服務架構原理三、跳板機服務安裝過程第一步&#xff1a;安裝跳板機依賴軟件yum -y install git python-pip mariadb-devel gcc automake autoconf python-devel readl…

【詳細圖解】再次理解im2col

【詳細圖解】再次理解im2col 轉自&#xff1a;https://mp.weixin.qq.com/s/GPDYKQlIOq6Su0Ta9ipzig 一句話&#xff1a;im2col是將一個[C,H,W]矩陣變成一個[H,W]矩陣的一個方法&#xff0c;其原理是利用了行列式進行等價轉換。 為什么要做im2col? 減少調用gemm的次數。 重要…

反思 大班 快樂的機器人_幼兒園大班教案《快樂的桌椅》含反思

大班教案《快樂的桌椅》含反思適用于大班的體育主題教學活動當中&#xff0c;讓幼兒提高協調性和靈敏性&#xff0c;創新桌椅的玩法&#xff0c;正確爬的方法&#xff0c;學會匍匐前進&#xff0c;快來看看幼兒園大班《快樂的桌椅》含反思教案吧。幼兒園大班教案《快樂的桌椅》…

DCN可形變卷積實現1:Python實現

DCN可形變卷積實現1&#xff1a;Python實現 我們會先用純 Python 實現一個 Pytorch 版本的 DCN &#xff0c;然后實現其 C/CUDA 版本。 本文主要關注 DCN 可形變卷積的代碼實現&#xff0c;不會過多的介紹其思想&#xff0c;如有興趣&#xff0c;請參考論文原文&#xff1a; …

藍牙耳機聲音一頓一頓的_線控耳機黨陣地轉移成功,OPPO這款TWS耳機體驗滿分...

“你看到我手機里3.5mm的耳機孔了嗎”&#xff0c;這可能是許多線控耳機黨最想說的話了。確實&#xff0c;如今手機在做“減法”&#xff0c;而廠商們首先就拿3.5mm耳機孔“開刀”&#xff0c;我們也喪失了半夜邊充電邊戴耳機打游戲的樂趣。竟然如此&#xff0c;那如何在耳機、…

AI移動端優化之Im2Col+Pack+Sgemm

AI移動端優化之Im2ColPackSgemm 轉自&#xff1a;https://blog.csdn.net/just_sort/article/details/108412760 這篇文章是基于NCNN的Sgemm卷積為大家介紹Im2ColPackSgemm的原理以及算法實現&#xff0c;希望對算法優化感興趣或者做深度學習模型部署的讀者帶來幫助。 1. 前言 …

elementui的upload組件怎么獲取上傳的文本流、_抖音feed流直播間引流你還不會玩?實操講解...

本文由艾奇在線明星優化師寫作計劃出品在這個全民驚恐多災多難且帶有魔幻的2020&#xff0c;一場突如其來的疫情改變了人們很多消費習慣&#xff0c;同時加速了直播電商的發展&#xff0c;現在直播已經成為商家必爭的營銷之地&#xff0c;直播雖然很火&#xff0c;但如果沒有流…

FFmpeg 視頻處理入門教程

FFmpeg 視頻處理入門教程 轉自&#xff1a;https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2020年1月14日 FFmpeg 是視頻處理最常用的開源軟件。 它功能強大&#xff0c;用途廣泛&#xff0c;大量用于視頻網站和商業軟件&…

checkbox wpf 改變框的大小_【論文閱讀】傾斜目標范圍框(標注)的終極方案

前言最常用的斜框標注方式是在正框的基礎上加一個旋轉角度θ&#xff0c;其代數表示為(x_c,y_c,w,h,θ)&#xff0c;其中(x_c,y_c )表示范圍框中心點坐標&#xff0c;(w,h)表示范圍框的寬和高[1,2,7]。對于該標注方式&#xff0c;如果將w和h的值互換&#xff0c;再將θ加上或者…

徹底理解BP之手寫BP圖像分類你也行

徹底理解BP之手寫BP圖像分類你也行 轉自&#xff1a;https://zhuanlan.zhihu.com/p/397963213 第一節&#xff1a;用矩陣的視角&#xff0c;看懂BP的網絡圖 1.1、什么是BP反向傳播算法 BP(Back Propagation)誤差反向傳播算法&#xff0c;使用反向傳播算法的多層感知器又稱為B…

h5頁面禁止復制_H5移動端頁面禁止復制技巧

前言&#xff1a;業務需要&#xff0c;需要對整個頁面禁止彈出復制菜單。在禁止的頁面中加入以下css樣式定義* {-webkit-touch-callout:none;/*系統默認菜單被禁用*/-webkit-user-select:none;/*webkit瀏覽器*/-khtml-user-select:none;/*早起瀏覽器*/-moz-user-select:none;/*…

梯度下降法和牛頓法計算開根號

梯度下降法和牛頓法計算開根號 本文將介紹如何不調包&#xff0c;只能使用加減乘除法實現對根號x的求解。主要介紹梯度下降和牛頓法者兩種方法&#xff0c;并給出 C 實現。 梯度下降法 思路/步驟 轉化問題&#xff0c;將 x\sqrt{x}x? 的求解轉化為最小化目標函數&#xff…

匯博工業機器人碼垛機怎么寫_全自動碼垛機器人在企業生產中的地位越來越重要...

全自動碼垛機器人在企業生產中的地位越來越重要在智能化的各種全自動生產線中&#xff0c;全自動碼垛機器人成了全自動生產線的重要機械設備&#xff0c;在各種生產中發揮著不可忽視的作用。全自動碼垛機器人主要用于生產線上的包裝過程中&#xff0c;不僅能夠提高企業的生產率…