【淺學】tflite-micro + ESP32S3 + VScode + ESP-IDF 基于例程快速實現自己的圖像分類模型訓練部署全流程

如果你用Pytorch訓練的模型那么可以參考我的步驟,使用的是Tensorflow的話參考官方文檔即可,但流程都是一樣的,每一步我都會提到部分操作細節及注意事項

官方教程

要詳細學習的話tflite-micro里的微控制器章節下都詳細看(頁面左側目錄最下方),要先嘗試跑通就直接跳到下一節內容跟著做,
在這里插入圖片描述

一切的開始,環境踩坑提示(已經有模型不需要訓練可跳過)

如果是自己用搭建模型結構從0訓練,按正常模型構建、訓練流程即可,要用預訓練的模型的話可以參考官方步驟,本筆記僅為個人探索過程的記錄不詳細展開模型訓練過程了,最后要導出的模型為文件為.tflite格式的,建議不要用太高版本的tensorflow,詳情看tensorflow官方的tflite micor相關說明,關于版本可能會踩非常多坑,如果用pytorch的話會用到onnx進行轉化,也有版本的坑,所以友情提示把以下需要確認對應版本在官方文檔里找到,新建一個虛擬環境,然后先初步過一遍確定沒有版本沖突再開始自己的模型代碼開發,否則環境的坑會導致不停重來,一定要新建一個虛擬環境,不要偷懶用自己已有的環境!!!!

  • GPU驅動
  • CUDA版本
  • pytorch/tensorflow(二選一,推薦tensorflow)和python版本
  • Numpy版本
  • onnx、onnxtf、onnxruntime版本(pytorch)

我用的是服務器,CUDA11.2,python3.9,主Pytorch ,本地的ESP-IDF版本是5.4.1,這里貼一個我的環境版本(太長了放在另一篇筆記里),

模型訓練注意事項

官方文檔里有這樣的提示
在這里插入圖片描述

使用預訓練模型

如果要用預訓練模型的話一定要參考tflite-micro官方文檔確定可支持的模型有哪些,并不是在PC上能跑通就可以的,還要考慮在ESP32上的部署。

自己構建模型從0訓練

不管是Pytorch還是tensorflow框架,僅使用tflite-micro支持的算子,否則就要自己添加自定義算子,我碰到了這個坑感興趣可以看我的這篇筆記【踩坑隨筆】TensorFlowLite_ESP32庫中不包含REDUCE_PROD算子,手動移植

預處理注意事項

一定要確定模型的輸入輸出以及圖片的預處理,我的模型輸入為[1,3,64,64], 轉化部署模型考慮采用uint8量化,所以在訓練環節的預處理我直接不做0~1的歸一化,而是采用[0,255],以下是我的預處理代碼,重點是最后的歸一化操作用transforms.Lambda(lambda x: x * 255)而不用Normalize

def get_transforms():train_transform = transforms.Compose([transforms.Resize((72, 72)),transforms.RandomCrop(64),transforms.RandomHorizontalFlip(0.5),transforms.RandomRotation(10),transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),transforms.ToTensor(),# transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])transforms.Lambda(lambda x: x * 255)  # 轉為 [0,255]])val_transform = transforms.Compose([transforms.Resize((64, 64)),transforms.ToTensor(),# transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])transforms.Lambda(lambda x: x * 255)])return train_transform, val_transform

模型導出轉換

Tensorflow模型可以直接轉存tflite格式,前面提到我采用的是Pytorch導出的是pth格式,所以走pth–>onnx->tensorflow模型–>tflite模型的路線,這個思路是ai生成的,可能會有冗余,感覺饒了很大的彎子,等我探索完有更好的方式再來更新

pth轉tflite(INT8量化)

參數里的路徑只是示例,改成你自己的對應文件路徑即可,model_path是訓練完保存好的pth格式的模型,data_path是數據集的文件夾,output_dir是轉換完保存tflite格式的模型的文件夾,重點注意converter.inference_input_type = tf.uint8,這決定了部署模型的輸入

def pytorch_to_tflite(model_path="./models/best_model.pth", data_path="./datasets49",output_dir="./models/tfmodels"):"""將PyTorch模型轉換為TensorFlow Lite模型"""print("Starting model conversion process...")# 1. 加載PyTorch模型print("Loading PyTorch model...")checkpoint = torch.load(model_path, map_location='cpu')num_classes = checkpoint['num_classes']model = ESPNetV2(num_classes=num_classes)model.load_state_dict(checkpoint['model_state_dict'])model.eval()print(f"Model loaded with {num_classes} classes")# 2. 準備校準數據print("Preparing calibration data...")_, val_transform = get_transforms()val_dataset = CustomImageDataset(data_path, transform=val_transform, train=False)val_loader = DataLoader(val_dataset, batch_size=1, shuffle=False)# 3. 導出為ONNXprint("Exporting to ONNX...")dummy_input = torch.randn(1, 3, 64, 64)onnx_path = os.path.join(output_dir, "model.onnx")torch.onnx.export(model,dummy_input,onnx_path,export_params=True,opset_version=11,do_constant_folding=True,input_names=['input'],output_names=['output'],dynamic_axes={'input': {0: 'batch_size'},'output': {0: 'batch_size'}})print(f"ONNX model saved to: {onnx_path}")# 4. 使用 onnx-tf 轉換為 TensorFlow SavedModelprint("Converting ONNX to TensorFlow using onnx-tf...")try:subprocess.check_call([sys.executable, "-m", "pip", "install", "onnx-tf"])from onnx_tf.backend import prepareexcept Exception as e:print("Failed to import or install onnx-tf:", e)returnonnx_model = onnx.load(onnx_path)tf_rep = prepare(onnx_model)tf_model_dir = output_dirtf_rep.export_graph(tf_model_dir)print(f"TensorFlow SavedModel saved to: {tf_model_dir}")# 5. 創建校準數據生成器def representative_dataset():for i, (data, _) in enumerate(val_loader):if i >= 100:  # 只使用100個樣本進行校準breakyield [data.numpy().astype(np.float32)]# 6. 轉換為TensorFlow Lite (INT8量化)print("Converting to TensorFlow Lite with INT8 quantization...")converter = tf.lite.TFLiteConverter.from_saved_model(tf_model_dir)converter.optimizations = [tf.lite.Optimize.DEFAULT]converter.representative_dataset = representative_datasetconverter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]  # 確保形狀操作兼容converter.inference_input_type = tf.uint8converter.inference_output_type = tf.uint8    tflite_model = converter.convert()# 保存TFLite模型tflite_path = os.path.join(output_dir, "model_quantized.tflite")with open(tflite_path, 'wb') as f:f.write(tflite_model)print(f"Quantized TFLite model saved to: {tflite_path}")print(f"Model size: {len(tflite_model) / 1024:.2f} KB")#  # 評估轉換后的模型# print("\nEvaluating converted model...")# evaluate_tflite_model(tflite_path, data_path)return tflite_path

提取tflite模型的量化參數和算子

這一步只是為了方便做驗證,如果對模型結構和轉化過程非常確定的話可以不提取,具體代碼看【淺學】從tflite模型提取算子和量化參數,會提取到以下信息,主要用來在部署中做驗證,包括輸入輸出格式、量化參數和采用的算子

{"input": {"name": "serving_default_input:0","shape": [1,3,64,64],"dtype": "<class 'numpy.uint8'>","scale": 1.0,"zero_point": 0},"output": {"name": "PartitionedCall:0","shape": [1,51],"dtype": "<class 'numpy.uint8'>","scale": 0.060637399554252625,"zero_point": 150}
}
inline tflite::MicroMutableOpResolver<8> CreateModelResolver() {tflite::MicroMutableOpResolver<8> resolver;// 注冊 CONV_2Dmicro_op_resolver.AddConv2d();// 注冊 DEPTHWISE_CONV_2Dmicro_op_resolver.AddDepthwiseConv2d();// 注冊 FULLY_CONNECTEDmicro_op_resolver.AddFullyConnected();// 注冊 MEANmicro_op_resolver.AddMean();// 注冊 PADmicro_op_resolver.AddPad();// 注冊 QUANTIZEmicro_op_resolver.AddQuantize();// 注冊 RESHAPEmicro_op_resolver.AddReshape();// 注冊 TRANSPOSEmicro_op_resolver.AddTranspose();return resolver;
}

上面提取生成的這個算子文件不能直接用哈,寫法不對,但是算子是對應的,需要對應上tflite-micro支持的算子,只是大小寫不一樣的話實際是同一個算子部署的代碼用對的寫法就行,但是如果提取到的算子在tflite-micro里沒有就要自定義了,參考tflite官方文檔

tflite轉C數組

模型訓練好導出.tflite格式后,可以通過執行以下命令轉換成C數組,這個在Windows系統上直接用Git bash切換到model.tflite文件夾下,然后執行這個命令(或者安裝了git配有git環境直接在終端里也可以)要注意文件路徑不能有空格和中文,然后我們就能得到一個model.cc文件,里面包含一個數組和一個表示數組大小的常量,確認一下數組大小,如果明顯比例程的大很多可能會導致Flash不夠用

$ xxd -i model.tflite > model.cc

圖片格式轉換

準備好你要進行圖像分類的10張圖片重命名為image0~image9,自己記錄好這10張圖片對應的類別方便你驗證最終推理結果對不對,然后進行轉換,轉換成二進制文件,一定要注意在轉換過程就做好了預處理,跟訓練環節的保持一致,convert_image_to_binary_pytorch(image_path, output_path, target_size=(64,64))這個函數里的對應你自己的輸入和預處理進行修改

import os
import glob
from PIL import Image
import numpy as np
import torchvision.transforms as transformsdef convert_image_to_binary_pytorch(image_path, output_path, target_size=(64,64)):"""使用 PyTorch transform 處理圖片并保存為二進制文件確保和訓練/驗證輸入一致"""# 打開圖片并轉換為 RGBimg = Image.open(image_path).convert('RGB')# 定義 transform,和驗證集一致transform = transforms.Compose([transforms.Resize(target_size),transforms.ToTensor(),          # float32, [0,1]transforms.Lambda(lambda x: x * 255)  # float32, [0,255]])img_tensor = transform(img)         # C,H,W, float32img_tensor = img_tensor.byte().numpy()  # uint8# 保證是 NCHWimg_tensor = img_tensor.astype(np.uint8)# 保存二進制文件with open(output_path, 'wb') as f:f.write(img_tensor.tobytes())# 打印信息,便于檢查print(f"Converted {os.path.basename(image_path)} -> {os.path.basename(output_path)}")print(f"Shape: {img_tensor.shape}, dtype: {img_tensor.dtype}, min/max: {img_tensor.min()}/{img_tensor.max()}\n")return output_pathdef convert_folder_to_binary_pytorch(folder_path, output_folder=None):"""批量將文件夾下圖片轉換為二進制文件"""# 支持的圖片格式image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp']if output_folder is None:output_folder = folder_pathos.makedirs(output_folder, exist_ok=True)for ext in image_extensions:for image_path in glob.glob(os.path.join(folder_path, ext)):output_name = os.path.splitext(os.path.basename(image_path))[0]output_path = os.path.join(output_folder, output_name)try:convert_image_to_binary_pytorch(image_path, output_path)except Exception as e:print(f"Failed to convert {image_path}: {e}")# 使用示例
if __name__ == "__main__":folder_path = "orignal_img"output_folder = "images"if os.path.isdir(folder_path):convert_folder_to_binary_pytorch(folder_path, output_folder)else:print("錯誤:指定的路徑不是一個有效的文件夾")

模型和數據驗證

寫個腳本批量執行,分別使用轉換前的pth模型和tflite模型、原始圖片和轉換的二進制圖片進行組合推理驗證,如果原始模型原始圖片的推理結果有誤識別不用管,那也是正常的是模型準確率的問題,確保這四種組合的輸出結果都是一致的(誤識別的也是一樣的輸出),否則根據這四個推理結果進行對比去查是哪一個環節出的問題,同一原始圖片pth模型和tflite模型輸出一致確保轉換的tflite模型沒問題,tflite模型使用原始圖片和轉換的二進制圖片輸出一致說明轉換的圖片沒有問題

代碼部署

例程創建

步驟參照這篇vscode+ESP-IDF+ESP32S3N16R8跑通TensorFlow Lite Micro for Espressif Chipsets的hello_word例程,把例程hello_world換成
person_detection即可,先跑通這個例程確保你的環境和硬件都沒有問題

idf.py create-project-from-example "esp-tflite-micro:person_detection"

修改宏用圖片測試

例程直接運行成功使用攝像頭獲取圖片進行推理,然后我們選擇用本地二進制圖片測試,打開eap_main.h,取消#define CLI_ONLY_INFERENCE 1的注釋

// Enable this to do inference on embedded images
#define CLI_ONLY_INFERENCE 1

運行成功的話在串口的終端輸入detect_image 0 (0~9的任意數字都可以),就可以看到檢測結果了。!!!!!!以下的前提是先把例程跑通哈!例程的README,md里有例程操作說明

模型加載

然后把前面轉化的model.cc復制到main文件夾下,打開,第一行添加頭文件#include "model.h",把model_tflite[]數組類型改成下面這樣,只改類型其他不動

#include "model.h"alignas(8) const unsigned char model_tflite[] = {....};
const int model_tflite_len = 12345;

然后再main文件夾下新建一個model.h,輸入以下代碼

#ifndef MODEL_H
#define MODEL_Hextern const unsigned char model_tflite[];
extern const int model_tflite_len;#endif

然后打開main文件夾下的CMakeLists.txt,SRCS后面添加“model.cc”

idf_component_register(SRCS"detection_responder.cc""image_provider.cc""main.cc""main_functions.cc""model_settings.cc""person_detect_model_data.cc""app_camera_esp.c""esp_cli.c""model.cc"# PRIV_REQUIRES console static_images spi_flashPRIV_REQUIRES console test_images spi_flashINCLUDE_DIRS "")

打開main文件夾下的main_functions.cc,找到void setup()函數,修改模型加載,把原來加載的例程的模型g_person_detect_model_data換成了我們自己的模型model_tflite

void setup() {// Map the model into a usable data structure. This doesn't involve any// copying or parsing, it's a very lightweight operation.// model = tflite::GetModel(g_person_detect_model_data);model = tflite::GetModel(model_tflite);

然后往下滑找到算子注冊的部分,替換成你自己的算子,比如我改成我自己用到的算子(跟前面提取的是對應的)
在這里插入圖片描述

模型設置

打開main文件夾的model_settings.cc,把kCategoryLabels[kCategoryCount]數組中的類別替換為你的類別

打開main文件夾的model_settings.h,把下面的參數修改為你的輸入尺寸,比如我的是(1,3,64,64),對應NCHW,我的類別總共有51類,輸出為(1,51),對應修改以下內容

constexpr int kNumCols = 64; //W
constexpr int kNumRows = 64; //H
constexpr int kNumChannels = 3; //Cconstexpr int kCategoryCount = 51; //類別

圖片數據替換

找到static_images文件夾中的sample_iamges文件夾,把里面的image圖片文件刪掉,把我們前面轉換好的自己的沒有后綴的image0~image9復制到這個文件夾下

推理代碼修改

打開main文件夾下的main_functions.cc文件,void run_inference(void *ptr)函數里第一個#if到最后一個#endif的內容不動,其他內容刪掉,然后修改成以下代碼,結合注釋自行理解一下,跟PC端的推理操作其實是一樣的步驟

void run_inference(void *ptr) {memcpy(input->data.uint8, ptr, input->bytes);#if defined(COLLECT_CPU_STATS)long long start_time = esp_timer_get_time();
#endif// Run the model on this input and make sure it succeeds.if (kTfLiteOk != interpreter->Invoke()) {MicroPrintf("Invoke failed.");}#if defined(COLLECT_CPU_STATS)long long total_time = (esp_timer_get_time() - start_time);printf("Total time = %lld\n", total_time / 1000);//printf("Softmax time = %lld\n", softmax_total_time / 1000);printf("FC time = %lld\n", fc_total_time / 1000);printf("DC time = %lld\n", dc_total_time / 1000);printf("conv time = %lld\n", conv_total_time / 1000);printf("Pooling time = %lld\n", pooling_total_time / 1000);printf("add time = %lld\n", add_total_time / 1000);printf("mul time = %lld\n", mul_total_time / 1000);/* Reset times */total_time = 0;//softmax_total_time = 0;dc_total_time = 0;conv_total_time = 0;fc_total_time = 0;pooling_total_time = 0;add_total_time = 0;mul_total_time = 0;
#endifTfLiteTensor* output = interpreter->output(0);float output_probs[kCategoryCount];float sum = 0.0f;// 量化 uint8 -> float (反量化)for (int i = 0; i < kCategoryCount; i++) {output_probs[i] = (output->data.uint8[i] - output->params.zero_point) * output->params.scale;sum += expf(output_probs[i]);  // softmax}// 計算 softmax 概率static float max_val = output_probs[0];for (int i = 1; i < kCategoryCount; i++) {if (output_probs[i] > max_val) max_val = output_probs[i];}float sum_exp = 0.0f;for (int i = 0; i < kCategoryCount; i++) {output_probs[i] = expf(output_probs[i] - max_val); // 防止 exp 溢出sum_exp += output_probs[i];}int max_idx = 0;float max_prob = 0.0f;for (int i = 0; i < kCategoryCount; i++) {output_probs[i] /= sum_exp;if (output_probs[i] > max_prob) {max_prob = output_probs[i];max_idx = i;}}int category_score_int = (max_prob) * 100 + 0.5;MicroPrintf("Detected: %s, score: %d%%",kCategoryLabels[max_idx], category_score_int);;
}

到這里就可以編譯運行了,前提是先把例程跑通哈!例程的README,md里有例程操作說明!!!!

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

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

相關文章

【HarmonyOS】應用設置全屏和安全區域詳解

【HarmonyOS】應用設置全屏和安全區域詳解 一、前言 IDE創建的鴻蒙應用&#xff0c;默認采取組件安全區布局方案。頂部會預留狀態欄區域&#xff0c;底部會預留導航條區域。這就是所謂的安全區域。 如果不處理&#xff0c;界面效果很割裂。所以業內UI交互設計&#xff0c;都會設…

openfeign 只有接口如何創建bean的

OpenFeign 能夠為純接口創建 Spring Bean&#xff0c;其核心機制是通過動態代理和 Spring 的 FactoryBean 機制實現的。以下是詳細的工作原理&#xff1a;1. EnableFeignClients 注解的啟動在 Spring Boot 主類上添加 EnableFeignClients 注解&#xff1a;SpringBootApplicatio…

【展廳多媒體】互動地磚屏怎么提升展廳互動感的?

在數字化展廳設計中&#xff0c;互動地磚屏 正成為提升觀眾參與度的重要工具。這種融合視覺科技與交互體驗的裝置&#xff0c;通過動態影像與即時反饋&#xff0c;讓參觀者從被動觀看轉變為主動探索&#xff0c;從而大幅增強展廳的互動感。 Led地面互動屏的優勢在于其強大的視…

AI賦能電力巡檢:變壓器漏油智能檢測系統全解析

&#x1f525; AI賦能電力巡檢&#xff1a;變壓器漏油智能檢測系統全解析 &#x1f4d6; 前言 在電力系統的日常運維中&#xff0c;變壓器作為核心設備&#xff0c;其安全運行直接關系到整個電網的穩定性。傳統的人工巡檢方式不僅效率低下&#xff0c;還存在安全隱患和漏檢風險…

GitHub上值得Star的計算機視覺項目

GitHub上值得Star的計算機視覺項目 前言 一、OpenCV:計算機視覺領域的瑞士軍刀 1.1 項目簡介 1.2 核心功能與技術特點 1.3 代碼示例 二、YOLO 系列:實時目標檢測的領導者 2.1 項目簡介 2.2 核心功能與技術特點 2.3 代碼示例 三、Detectron2:Facebook AI Research 的目標檢測…

【深度學習】pytorch深度學習框架的環境配置

文章目錄1. 配置cuda環境2. 配置conda環境3. 配置pytorch gpu環境1. 配置cuda環境 在命令行輸入以下命令可以查看當前顯卡驅動版本和最高支持的cuda版本 nvidia-smi根據cuda版本去官網下載并安裝cuda 下載鏈接&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive…

數據處理與統計分析 —— 房源數據集分析案例

數據集網盤下載&#xff1a; 鏈接&#xff1a;https://pan.quark.cn/s/0e577858dba3?pwdFJnb 提取碼&#xff1a;FJnb代碼僅供參考具體可打開ipynb文件進行學習和練習&#xff1a;鏈接&#xff1a;https://pan.quark.cn/s/8efbe3061fad?pwdT47B 提取碼&#xff1a;T47Bimport…

藍牙如何測試?

車載藍牙測試需覆蓋 連接穩定性、功能完整性、兼容性、交互體驗等核心維度,結合車載場景的特殊性(如行駛中信號干擾、多設備交互、安全需求),具體測試點如下: 一、基礎配對與連接測試 1. 首次配對 觸發配對:車機端 “藍牙設置” 中搜索設備、手機端搜索車機(車機名稱是…

算法02 二進制與位運算

二進制作為計算機底層數據的核心表示方式&#xff0c;其獨特的位結構和運算規則在算法設計中有著廣泛且關鍵的應用。以下從基礎操作、算法技巧、數據結構、經典問題等多個維度&#xff0c;全面梳理二進制在算法中的應用&#xff1a; 一、基礎位運算&#xff1a;算法的“原子操作…

PAT 1071 Speech Patterns

題目大意是說給出一個文本&#xff0c;找出里面出現最多的單詞&#xff0c;如果有多個單詞出現次數一樣多&#xff0c;則輸出字典序最小的。 需要注意的是&#xff1a; 給出的文本字符串不僅有數字還有字母&#xff0c;還有一些特殊的字符&#xff0c;還有空格。 而單詞是只包含…

CSS中的 :root 偽類

在CSS中&#xff0c;偽類是一種用于選擇元素特定狀態的選擇器。:root 偽類專門用于選擇文檔的根元素&#xff08;在HTML中通常是<html>元素&#xff09;&#xff0c;它是CSS變量&#xff08;Custom Properties&#xff09;的理想載體&#xff0c;常用于定義全局樣式變量&…

能源行業數字化轉型:邊緣計算網關在油田場景的深度應用

能源行業數字化轉型&#xff1a;邊緣計算網關在油田場景的深度應用能源行業是國民經濟的支柱產業&#xff0c;而油田作為能源生產的重要基地&#xff0c;其數字化轉型對于提高生產效率、降低能耗、減少碳排放具有重要意義。然而&#xff0c;油田往往地處偏遠&#xff0c;油井分…

CAG緩存增強生成與RAG檢索增強生成對比

深度定制 LLM 知識,除了 RAC &#xff0c;現在又有新技術假設有一份200頁的產品手冊,你想讓 LLM 準確回答里面的相關問題,要實現這個目標,除了常用的檢索增強生成技術 rep ,現在有了新思路,緩存增強生成 CAG &#xff0c;它是什么,何時使用.RAG檢索增強是常規套路,CAG緩存增強是…

基于vue、node.js、express的網絡教學系統設計與實現/基于vue、node.js、express的在線學習系統設計與實現

基于vue、node.js、express的網絡教學系統設計與實現/基于vue、node.js、express的在線學習系統設計與實現

享元模式引發的關于ECS和對象池的思考記錄

文章目錄概念概述解決了什么區別與聯系享元模式的某個例子的細節分析概念概述 ECS&#xff08;Entity-Component-System&#xff09; 1、Entity&#xff08;實體&#xff09;&#xff1a;唯一標識符。 2、Component&#xff08;組件&#xff09;&#xff1a;純數據容器&#x…

STM32驅動SG90舵機全解析:從PWM原理到多舵機協同控制

一、SG90舵機核心特性 1.1 基本參數與選型 SG90作為??微型舵機的代表??,憑借其??輕量化設計??(僅9g)和??高性價比??,在機器人、智能小車和云臺系統中廣泛應用: ??關鍵參數對比??: ??參數?? 180定位舵機 360連續旋轉舵機 ??控制目標?? 精確…

goland怎么取消自動刪除未使用的包

1.settings-Go-Imports-取消勾選Optimize imports on the fly2.settings-Tools-取消勾選Optimize imports

halcon基于透視的可變形模型匹配

算子1&#xff0c;create_planar_uncalib_deformable_model_xld***基于平面未校準的輪廓模型算子2&#xff0c;find_planar_uncalib_deformable_model***查找平面未校準可變形模型算子3&#xff0c;projective_trans_contour_xld***將輪廓進行透視變換附加算子 算子4read_conto…

Flink Stream API - 源碼開發需求描述

概述 本文介紹如何基于Flink源碼進行二次開發&#xff0c;實現一個動態規則引擎系統。通過自定義算子和算子協調器&#xff0c;實現數據流的動態規則計算和協調管理。以此更好理解前面介紹的源碼相關文章 項目需求 核心功能 實現一個動態規則引擎&#xff0c;具備以下特性&…

「 CentOS7 安裝部署k8s」

一、Linux系統部署K8s還是非常便利的&#xff0c;只需要掌握Linux常用命令&#xff0c;便可以迅速部署&#xff0c;一起來學習一下吧1、運行以下命令更新系統并安裝必要工具&#xff1a;yum update -y yum install -y yum-utils device-mapper-persistent-data lvm22、安裝Dock…