【OpenVINO】基於 OpenVINO C++ API 部署 RT-DETR 模型

来源:https://www.cnblogs.com/guojin-blogs/p/17976138
-Advertisement-
Play Games

在該文章中,我們基於OpenVINO™ Python API 向大家展示了包含後處理的RT-DETR模型的部署流程,但在實際工業應用中,我們為了與當前軟體平臺集成更多會採用C++平臺,因此在本文中,我們將基於OpenVINO™ C++ API 向大家展示了不包含後處理的RT-DETR模型的部署流程,... ...


  RT-DETR是在DETR模型基礎上進行改進的,一種基於 DETR 架構的實時端到端檢測器,它通過使用一系列新的技術和演算法,實現了更高效的訓練和推理,在前文我們發表了《基於 OpenVINO™ Python API 部署 RT-DETR 模型 | 開發者實戰》,在該文章中,我們基於OpenVINO™ Python API 向大家展示了包含後處理的RT-DETR模型的部署流程,但在實際工業應用中,我們為了與當前軟體平臺集成更多會採用C++平臺,因此在本文中,我們將基於OpenVINO™ C++ API 向大家展示了不包含後處理的RT-DETR模型的部署流程,並向大家展示如何導出不包含後處理的RT-DETR模型。
  該項目所使用的全部代碼已經在GitHub上開源,並且收藏在OpenVINO-CSharp-API項目里,項目所在目錄鏈接為:

https://github.com/guojin-yan/OpenVINO-CSharp-API/tree/csharp3.0/tutorial_examples

  也可以直接訪問該項目,項目鏈接為:

https://github.com/guojin-yan/RT-DETR-OpenVINO.git

項目首髮網址為:基於 OpenVINO™ C++ API 部署 RT-DETR 模型 | 開發者實戰

1. RT-DETR

  飛槳在去年 3 月份推出了高精度通用目標檢測模型 PP-YOLOE ,同年在 PP-YOLOE 的基礎上提出了 PP-YOLOE+。而繼 PP-YOLOE 提出後,MT-YOLOv6、YOLOv7、DAMO-YOLO、RTMDet 等模型先後被提出,一直迭代到今年開年的 YOLOv8。

image

  YOLO 檢測器有個較大的待改進點是需要 NMS 後處理,其通常難以優化且不夠魯棒,因此檢測器的速度存在延遲。DETR是一種不需要 NMS 後處理、基於 Transformer 的端到端目標檢測器。百度飛槳正式推出了——RT-DETR (Real-Time DEtection TRansformer) ,一種基於 DETR 架構的實時端到端檢測器,其在速度和精度上取得了 SOTA 性能。

image

  RT-DETR是在DETR模型基礎上進行改進的,它通過使用一系列新的技術和演算法,實現了更高效的訓練和推理。具體來說,RT-DETR具有以下優勢:

  • 1、實時性能更佳:RT-DETR採用了一種新的註意力機制,能夠更好地捕獲物體之間的關係,並減少計算量。此外,RT-DETR還引入了一種基於時間的註意力機制,能夠更好地處理視頻數據。
  • 2、精度更高:RT-DETR在保證實時性能的同時,還能夠保持較高的檢測精度。這主要得益於RT-DETR引入的一種新的多任務學習機制,能夠更好地利用訓練數據。
  • 3、更易於訓練和調參:RT-DETR採用了一種新的損失函數,能夠更好地進行訓練和調參。此外,RT-DETR還引入了一種新的數據增強技術,能夠更好地利用訓練數據。

image

2. OpenVINO

  英特爾發行版 OpenVINO™工具套件基於oneAPI 而開發,可以加快高性能電腦視覺和深度學習視覺應用開發速度工具套件,適用於從邊緣到雲的各種英特爾平臺上,幫助用戶更快地將更準確的真實世界結果部署到生產系統中。通過簡化的開發工作流程, OpenVINO™可賦能開發者在現實世界中部署高性能應用程式和演算法。

image

  OpenVINO™ 2023.1於2023年9月18日發佈,該工具包帶來了挖掘生成人工智慧全部潛力的新功能。生成人工智慧的覆蓋範圍得到了擴展,通過PyTorch*等框架增強了體驗,您可以在其中自動導入和轉換模型。大型語言模型(LLM)在運行時性能和記憶體優化方面得到了提升。聊天機器人、代碼生成等的模型已啟用。OpenVINO更便攜,性能更高,可以在任何需要的地方運行:在邊緣、雲中或本地。

3. 環境配置

  在上一篇文章中我們以已經向大家提供了RT-DETR模型導出蘇需要的環境,此處不再多做展示,為了大家更好的復現該項目代碼,此處向大家提供本次開發所使用的C++環境:

openvino: 2023.1.0
opencv: 4.5.5

  大家在復現代碼時可以使用相同的環境或者與作者所使用環境發佈較為接近的環境進行開發,防止使用時出現不必要的錯誤;此外該項目提供了兩種編譯方式,大家可以使用Visual Studio進行編譯以及CMake進行編譯。

4. 模型下載與轉換

  在上一篇文章中我們已經向大家展示了RT-DETR預訓練模型的導出方式,該模型是預設包含後處理的;因此在本文中,我們將向大家展示不包含後處理的RT-DETR模型導出方式以及兩種模型的差異。

4.1 模型導出

  PaddleDetection官方庫向我們提供了十分友好API介面,因此導出不包含後處理的RT-DETR模型也是十分容易的。首先修改配置文件,主要是修改RT-DETR模型的配置文件,配置文件路徑為:.\PaddleDetection\configs\rtdetr\_base_\rtdetr_r50vd.yml,在配置文件DETR項目下增加exclude_post_process: True語句,如下圖所示:

image

  然後重新運行模型導出指令,便可以獲取不包含後處理的模型:

python tools/export_model.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml -o weights=https://bj.bcebos.com/v1/paddledet/models/rtdetr_r50vd_6x_coco.pdparams trt=True --output_dir=output_inference

  在模型導出後,我們可以轉換成ONNX格式以及IR格式,可參考上一篇文章中的模型轉換內容。

4.2 模型信息對比

  通過下表我們可以看出,裁剪後的模型,只包含一個輸入節點,其輸出節點也發生了變化,原本模型輸出為處理後的預測輸出,經過裁剪後,模型輸出節點輸出內容發生了較大變化。其中:

  • stack_7.tmp_0.slice_0:該節點表示300種預測結果的預測框信息;
  • stack_8.tmp_0.slice_0:該節點表示300種預測結果的80種分類信息置信度,後續再處理時,需要根據預測結果獲取最終的預測分類信息。

image
image
image

5. C++代碼實現

  為了更系統地實現RT-DETR模型的推理流程,我們採用C++特性,封裝了RTDETRPredictor模型推理類以及RTDETRProcess模型數據處理類,下麵我們將對這兩個類中的關鍵代碼進行講解。

5.1 模型推理類實現

  C++代碼中我們定義的RTDETRPredictor模型推理類如下所示:

class RTDETRPredictor
{
public:
    RTDETRPredictor(std::string model_path, std::string label_path, 
        std::string device_name = "CPU", bool postprcoess = true);
    cv::Mat predict(cv::Mat image);
private:
    void pritf_model_info(std::shared_ptr<ov::Model> model);
    void fill_tensor_data_image(ov::Tensor& input_tensor, const cv::Mat& input_image);
    void fill_tensor_data_float(ov::Tensor& input_tensor, float* input_data, int data_size);
private:
    RTDETRProcess rtdetr_process;
    bool post_flag;
    ov::Core core;
    std::shared_ptr<ov::Model> model;
    ov::CompiledModel compiled_model;
    ov::InferRequest infer_request;
};
    1. 模型推理類初始化
      首先我們需要初始化模型推理類,初始化相關信息:
RTDETRPredictor::RTDETRPredictor(std::string model_path, std::string label_path, 
std::string device_name, bool post_flag)
	:post_flag(post_flag){
    INFO("Model path: " + model_path);
    INFO("Device name: " + device_name);
    model = core.read_model(model_path);
    pritf_model_info(model);
    compiled_model = core.compile_model(model, device_name);
    infer_request = compiled_model.create_infer_request();
    rtdetr_process = RTDETRProcess(cv::Size(640, 640), label_path, 0.5);
}

  在該方法中主要包含以下幾個輸入:

  • model_path:推理模型地址;
  • label_path:模型預測類別文件;
  • device_name:推理設備名稱;
  • post_flag:模型是否包含後處理,當post_flag = true時,包含後處理,當post_flag = false時,不包含後處理。
    1. 圖片預測API
      這一步中主要是對輸入圖片進行預測,並將模型預測結果會知道輸入圖片上,下麵是這階段的主要代碼:
cv::Mat RTDETRPredictor::predict(cv::Mat image){
    cv::Mat blob_image = rtdetr_process.preprocess(image);
    if (post_flag) {
        ov::Tensor image_tensor = infer_request.get_tensor("image");
        ov::Tensor shape_tensor = infer_request.get_tensor("im_shape");
        ov::Tensor scale_tensor = infer_request.get_tensor("scale_factor");
        image_tensor.set_shape({ 1,3,640,640 });
        shape_tensor.set_shape({ 1,2 });
        scale_tensor.set_shape({ 1,2 });
        fill_tensor_data_image(image_tensor, blob_image);
        fill_tensor_data_float(shape_tensor, rtdetr_process.get_input_shape().data(), 2);
        fill_tensor_data_float(scale_tensor, rtdetr_process.get_scale_factor().data(), 2);
    } else {
        ov::Tensor image_tensor = infer_request.get_input_tensor();
        fill_tensor_data_image(image_tensor, blob_image);
    }
    infer_request.infer();
    ResultData results;
    if (post_flag) {
        ov::Tensor output_tensor = infer_request.get_tensor("reshape2_95.tmp_0");
        float result[6 * 300] = {0};
        for (int i = 0; i < 6 * 300; ++i) {
            result[i] = output_tensor.data<float>()[i];
        }
        results = rtdetr_process.postprocess(result, nullptr, true);
    } else {
        ov::Tensor score_tensor = infer_request.get_tensor(model->outputs()[1].get_any_name());
        ov::Tensor bbox_tensor = infer_request.get_tensor(model->outputs()[0].get_any_name());
        float score[300 * 80] = {0};
        float bbox[300 * 4] = {0};
        for (int i = 0; i < 300; ++i) {
            for (int j = 0; j < 80; ++j) {
                score[80 * i + j] = score_tensor.data<float>()[80 * i + j];
            }
            for (int j = 0; j < 4; ++j) {
                bbox[4 * i + j] = bbox_tensor.data<float>()[4 * i + j];
            }
        }
        results = rtdetr_process.postprocess(score, bbox, false);
    }
    return rtdetr_process.draw_box(image, results);
}

  上述代碼的主要邏輯如下:首先是處理輸入圖片,調用定義的數據處理類,將輸入圖片處理成指定的數據類型;然後根據模型的輸入節點情況配置模型輸入數據,如果使用的是動態模型輸入,需要設置輸入形狀;接下來就是進行模型推理;最後就是對推理結果進行處理,並將結果繪製到輸入圖片上。

5.2 模型數據處理類RTDETRProcess

    1. 定義RTDETRProcess
class RTDETRProcess
{
public:
    RTDETRProcess() {}
    RTDETRProcess(cv::Size target_size, std::string label_path = NULL, float threshold = 0.5,
        cv::InterpolationFlags interpf = cv::INTER_LINEAR);
    cv::Mat preprocess(cv::Mat image);
    ResultData postprocess(float* score, float* bboxs, bool post_flag);
    std::vector<float> get_im_shape() { return im_shape; }
    std::vector<float> get_input_shape() { return { (float)target_size.width ,(float)target_size.height }; }
    std::vector<float> get_scale_factor() { return scale_factor; }
    cv::Mat draw_box(cv::Mat image, ResultData results);
private:
    void read_labels(std::string label_path);
    template<class T>
    float sigmoid(T data) { return 1.0f / (1 + std::exp(-data));}
    template<class T>
    int argmax(T* data, int length) {
        std::vector<T> arr(data, data + length);
        return (int)(std::max_element(arr.begin(), arr.end()) - arr.begin());
    }
private:
    cv::Size target_size;               // The model input size.
    std::vector<std::string> labels;    // The model classification label.
    float threshold;                    // The threshold parameter.
    cv::InterpolationFlags interpf;     // The image scaling method.
    std::vector<float> im_shape;
    std::vector<float> scale_factor;
};
    1. 輸入數據處理方法
cv::Mat RTDETRProcess::preprocess(cv::Mat image){
    im_shape = { (float)image.rows, (float)image.cols };
    scale_factor = { 640.0f / (float)image.rows, 640.0f / (float)image.cols};
    cv::Mat blob_image;
    cv::cvtColor(image, blob_image, cv::COLOR_BGR2RGB); 
    cv::resize(blob_image, blob_image, target_size, 0, 0, cv::INTER_LINEAR);
    std::vector<cv::Mat> rgb_channels(3);
    cv::split(blob_image, rgb_channels);
    for (auto i = 0; i < rgb_channels.size(); i++) {
        rgb_channels[i].convertTo(rgb_channels[i], CV_32FC1, 1.0 / 255.0);
    }
    cv::merge(rgb_channels, blob_image);
    return blob_image;
}
    1. 預測結果數據處理方法
ResultData RTDETRProcess::postprocess(float* score, float* bbox, bool post_flag)
{
    ResultData result;
    if (post_flag) {
        for (int i = 0; i < 300; ++i) {
            if (score[6 * i + 1] > threshold) {
                result.clsids.push_back((int)score[6 * i ]);
                result.labels.push_back(labels[(int)score[6 * i]]);
                result.bboxs.push_back(cv::Rect(score[6 * i + 2], score[6 * i + 3],
                    score[6 * i + 4] - score[6 * i + 2],
                    score[6 * i + 5] - score[6 * i + 3]));
                result.scores.push_back(score[6 * i + 1]);
            }
        }
    } else {
        for (int i = 0; i < 300; ++i) {
            float s[80];
            for (int j = 0; j < 80; ++j) {
                s[j] = score[80 * i + j];
            }
            int clsid = argmax<float>(s, 80);
            float max_score = sigmoid<float>(s[clsid]);
            if (max_score > threshold) {
                result.clsids.push_back(clsid);
                result.labels.push_back(labels[clsid]);
                float cx = bbox[4 * i] * 640.0 / scale_factor[1];
                float cy = bbox[4 * i + 1] * 640.0 / scale_factor[0];
                float w = bbox[4 * i + 2] * 640.0 / scale_factor[1];
                float h = bbox[4 * i + 3] * 640.0 / scale_factor[0];
                result.bboxs.push_back(cv::Rect((int)(cx - w / 2), (int)(cy - h / 2), w, h));
                result.scores.push_back(max_score);
            }
        }
    }
    return result;
}

  此處對輸出結果做一個解釋,由於我們提供了兩種模型的輸出,此處提供了兩種模型的輸出數據處理方式,主要區別在於是否對預測框進行還原以及對預測類別進行提取,具體區別大家可以查看上述代碼。

6. 預測結果展示

  最後通過上述代碼,我們最終可以直接實現RT-DETR模型的推理部署,RT-DETR與訓練模型採用的是COCO數據集,最終我們可以獲取預測後的圖像結果,如圖所示:

image

  上圖中展示了RT-DETR模型預測結果,同時,我們對模型圖裡過程中的關鍵信息以及推理結果進行了列印:

[INFO]  This is an RT-DETR model deployment case using C++!
[INFO]  Model path: E:\\Model\\RT-DETR\\RTDETR_cropping\\rtdetr_r50vd_6x_coco.onnx
[INFO]  Device name: CPU
[INFO]  Inference Model
[INFO]    Model name: Model from PaddlePaddle.
[INFO]    Input:
[INFO]       name: image
[INFO]       type: float
[INFO]       shape: [?,3,640,640]
[INFO]    Output:
[INFO]       name: stack_7.tmp_0_slice_0
[INFO]       type: float
[INFO]       shape: [?,300,4]
[INFO]       name: stack_8.tmp_0_slice_0
[INFO]       type: float
[INFO]       shape: [?,300,80]
[INFO]  Infer result:
[INFO]    class_id : 0, label : person, confidence : 0.928, left_top : [215, 327], right_bottom: [259, 468]
[INFO]    class_id : 0, label : person, confidence : 0.923, left_top : [260, 343], right_bottom: [309, 460]
[INFO]    class_id : 0, label : person, confidence : 0.893, left_top : [402, 346], right_bottom: [451, 478]
[INFO]    class_id : 0, label : person, confidence : 0.796, left_top : [456, 369], right_bottom: [507, 479]
[INFO]    class_id : 0, label : person, confidence : 0.830, left_top : [519, 360], right_bottom: [583, 479]
[INFO]    class_id : 33, label : kite, confidence : 0.836, left_top : [323, 159], right_bottom: [465, 213]
[INFO]    class_id : 33, label : kite, confidence : 0.805, left_top : [329, 64], right_bottom: [388, 85]
[INFO]    class_id : 33, label : kite, confidence : 0.822, left_top : [282, 217], right_bottom: [419, 267]
[INFO]    class_id : 0, label : person, confidence : 0.834, left_top : [294, 384], right_bottom: [354, 443]
[INFO]    class_id : 33, label : kite, confidence : 0.793, left_top : [504, 195], right_bottom: [522, 214]
[INFO]    class_id : 33, label : kite, confidence : 0.524, left_top : [233, 22], right_bottom: [242, 29]
[INFO]    class_id : 33, label : kite, confidence : 0.763, left_top : [116, 178], right_bottom: [136, 190]
[INFO]    class_id : 0, label : person, confidence : 0.601, left_top : [497, 380], right_bottom: [529, 479]
[INFO]    class_id : 33, label : kite, confidence : 0.764, left_top : [460, 251], right_bottom: [478, 268]
[INFO]    class_id : 33, label : kite, confidence : 0.605, left_top : [176, 236], right_bottom: [256, 257]
[INFO]    class_id : 0, label : person, confidence : 0.732, left_top : [154, 380], right_bottom: [210, 420]
[INFO]    class_id : 33, label : kite, confidence : 0.574, left_top : [221, 264], right_bottom: [342, 312]
[INFO]    class_id : 33, label : kite, confidence : 0.588, left_top : [97, 316], right_bottom: [155, 359]
[INFO]    class_id : 33, label : kite, confidence : 0.523, left_top : [171, 317], right_bottom: [227, 357]
[INFO]    class_id : 33, label : kite, confidence : 0.657, left_top : [363, 120], right_bottom: [375, 129]
[INFO]    class_id : 0, label : person, confidence : 0.698, left_top : [26, 341], right_bottom: [57, 425]
[INFO]    class_id : 33, label : kite, confidence : 0.798, left_top : [242, 124], right_bottom: [263, 135]
[INFO]    class_id : 33, label : kite, confidence : 0.528, left_top : [218, 178], right_bottom: [451, 241]
[INFO]    class_id : 33, label : kite, confidence : 0.685, left_top : [430, 29], right_bottom: [449, 43]
[INFO]    class_id : 33, label : kite, confidence : 0.640, left_top : [363, 120], right_bottom: [375, 129]
[INFO]    class_id : 33, label : kite, confidence : 0.559, left_top : [161, 193], right_bottom: [171, 199]

7. 總結

  在本項目中,我們介紹了OpenVINO C++ API 部署自帶後處理的RT-DETR模型的案例,並結合該模型的處理方式封裝完整的代碼案例,實現了在 Intel 平臺使用OpenVINO 加速深度學習模型,有助於大家以後落地RT-DETR模型在工業上的應用。

  在下一篇文章《基於 OpenVINO Python C# 部署 RT-DETR 模型》中,我們將基於C# API介面,實現RT-DETR 模型的部署,並且基於開發的代碼,對比不同平臺的推理速度。如果大家有興趣,可以先關註本項目代碼倉庫,獲取項目實現源碼。
image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近,有群里在群里發了這麼一個非常有意思的卡片 Hover 動效,來源於此網站 -- key-drop,效果如下: 非常有意思酷炫的效果。而本文,我們不會完全還原此效果,而是基於此效果,嘗試去製作這麼一個類似的卡片交互效果: 該效果的幾個核心點: 卡片的 3D 旋轉跟隨滑鼠移動效果 如何讓卡片在 H ...
  • 一、定義 定義一個操作中演算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類不改變一個演算法的結構即可重定義該演算法的特定步驟。模板方法是一種類行為型模式 二、描述 模板方法模式結構比較簡單,其核心是抽象類和其中的模板方法的設計,包含以下兩個角色: 1、AbstractClass(抽象類):在抽象 ...
  • 本文在原文基礎上有刪減,原文參考泛型、Trait 和生命周期。 目錄泛型數據類型在函數定義中使用泛型結構體定義中的泛型枚舉定義中的泛型方法定義中的泛型泛型代碼的性能Trait:定義共同行為定義 trait為類型實現 trait預設實現trait 作為參數Trait Bound 語法通過 + 指定多個 ...
  • 一、pom.xml需要引入的依賴二、項目開啟熔斷器開關 2.1 註解方式 2.2 xml方式三、依賴類缺失問題四、版本匹配安全檢查問題五、測試驗證六、結論 一、pom.xml需要引入的依賴 pom.xml <!-- springboot升級到2.6.7,同樣適用於2.7.0,2.7.18等 --> ...
  • 看到標題大家可能會有點疑惑吧:OpenFeign 不是挺好用的嗎?尤其是微服務之間的遠程調用,平時用的也挺習慣的,為啥要替換呢? ...
  • 當然,我寫的簡易版協程池還有很多可以優化的地方,比如可以實現動態擴容等功能。今天我們要簡單總結一下協程池的優勢,主要是為了降低資源開銷。協程池的好處在於可以重覆利用協程,避免頻繁創建和銷毀協程,從而減少系統開銷,提高系統性能。此外,協程池還可以提高響應速度,因為一旦接收到任務,可以立即執行,不需要等... ...
  • ZooKeeperServer 實現了單機版zookeeper服務端功能,子類實現了更加豐富的分散式集群功能: ZooKeeperServer |-- QuorumZooKeeperServer |-- LeaderZooKeeperServer |-- LearnerZooKeeperServer ...
  • 臨時接到一個需求說讓根據按照下麵的這個圖片的結構來打包下載指定位置下的文件到指定位置! 實現思路: 1.把已經實現的樹形結構的代碼進行調用,拿到他的數據進行創建對應的文件夾 2.因為結構下方的文件沒有特別直觀的資料庫中的關聯關係,所以還需要對於管理關係進行梳理 3.創建好階級文件,然後調用網上找的工 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...