当前位置: 首页 > news >正文

VS2015 + OpenCV + OnnxRuntime-Cpp + YOLOv8 部署

  • 近期有个工作需求是进行 YOLOv8 模型的 C++ 部署,部署环境如下
    1. 系统:Windows
    2. IDE:VS2015
    3. 语言:C++
    4. OpenCV 4.5.0
    5. OnnxRuntime 1.15.1

0. 预训练模型保存为 .onnx 格式

  • 假设已经有使用 ultralytics 库训练并保存为 .pt 格式的 YOLOv8 模型,将其转换为 .onnx 格式是简单的
    import os
    import sys
    base_path = os.path.abspath(os.path.join(os.path.dirname(__file__),))
    sys.path.append(base_path)from ultralytics import YOLO# Load a YOLOv8 model
    model = YOLO(f"{base_path}/model/output/best.pt")# Export the model
    model.export(format="onnx", opset=9, simplify=True, dynamic=False, imgsz=640)
    

    注意导出时设置了 opset=9,这个版本可以和 OnnxRuntime 1.15.1 匹配

  • 除了 .onnx 模型文件以外,还需要准备数据集的 .yaml 文件和用于测试的图片

1. 依赖下载

1.1 OpenCV

  • 在 window 上用 Cmake 从源码编译 OpenCV 很麻烦,直接下载 release 库
  • 下载地址:OpenCV-4.5.0
    在这里插入图片描述
    下载后得到 opencv-4.5.0-vc14_vc15.exe,双击解压。把 C:\Users…\opencv\build\x64\vc14\bin 添加到环境变量。其中 vc14 对应 vs2015
  • 提取的文件中,以下是我们之后需要的
    1. 头文件:C:\Users…\opencv\build\include\opencv2
    2. 动态库:C:\Users…\opencv\build\x64\vc14\bin\opencv_world450.dll
    3. 静态库:C:\Users…\opencv\build\x64\vc14\lib\opencv_world450.lib & opencv_world450d.lib

      opencv_world450.lib 用于 vs release 模式;opencv_world450d.lib 用于 vs debug 模式

1.2 OnnxRuntime

  • 下载地址:ONNX Runtime v1.15.1,下载 onnxruntime-win-x64-1.15.1.zip,下载后直接解压。
  • 提取的文件中,以下是我们之后需要的
    1. 头文件:C:\Users…\onnxruntime-win-x64-1.15.1\include
    2. 动态库:C:\Users…\onnxruntime-win-x64-1.15.1\lib 下的所有 .dll 文件
    3. 静态库:C:\Users…\onnxruntime-win-x64-1.15.1\lib 下的所有 .lib 文件

1.3 Cpp 源码

  • 下载地址:YOLOv8 OnnxRuntime C++。这是 ultralytics 提供的官方案例,注意其依赖
    在这里插入图片描述
    由于 vs2015 无法设置 C++17 标准,后续会修改源码,去掉其中使用的 filesystem 库,由于仅部署 CPU 版本,无需 Cuda 和 cuDNN。另外这个 readme 还提到了 Cmake 编译,我们不需要做这步,直接用它的源码就行了

2. VS2015 工程配置

2.1 创建项目

  • 首先新建 Win32 控制台项目,选择空项目。我这里项目路径为 YOLOv8-Test,项目名为 Test。打开工程根目录,新建 bin、lib、include 三个目录
    在这里插入图片描述
    • 在 bin 中粘贴 1.2 和 1.3 节提到的所有动态库文件
      在这里插入图片描述
    • 在 lib 中粘贴 1.2 和 1.3 节提到的所有静态库文件
      在这里插入图片描述
    • 在 include 中粘贴 1.2 和 1.3 节提到的所有头文件目录
      在这里插入图片描述
      其中 onnxruntime-win-x64 就是 C:\Users…\onnxruntime-win-x64-1.15.1\include 文件夹
  • 把源码粘贴到根目录,并添加到项目中
    在这里插入图片描述

2.2 配置项目属性

  • 在解决方案名 Test 处右键,点最下面属性,打开项目属性页。首先配置 Release 属性,平台选择 x64
    在这里插入图片描述
    • VC++目录 -> 包含目录,编辑增加以下路径
      在这里插入图片描述

    • VC++目录 -> 库目录,编辑增加以下路径
      在这里插入图片描述

    • C/C++ -> 常规 -> 附加包含目录,编辑增加以下路径。并关闭 SDL 检查
      在这里插入图片描述

    • 链接器 -> 附加库目录,编辑增加以下路径
      在这里插入图片描述

    • 链接器 -> 输入 -> 附加依赖项,编辑增加以下文件名(主要这里是Release版本所以用 opencv_world450.lib)
      在这里插入图片描述

    • 类似地配置 Debug 属性,区别仅在于最后一步链接附加依赖项写入 opencv_world450d.lib

  • 由于 1.3 节没有对源码进行 Cmake 安装,还需要将 onnxruntime.dll 复制粘贴到编译运行后 .exe 文件的生成目录下,如 C:…\YOLOv8-Test\Test\x64\Release,以免出现链接错误
    在这里插入图片描述
    如果不想在每次编译都粘贴,可以直接把它粘贴到 C:\Windows\System32 和 C:\Windows\SysWOW64

3. 修改源码

  • 由于 vs2015 无法使用 C++17 特征,修改 main.cpp 去掉其中对 filesystem 库的依赖,如下

    #include <iostream>
    #include <iomanip>
    #include "inference.h"
    #include <fstream>
    #include <random>
    #include <vector>
    #include <string>
    #include <dirent.h>
    #include <sys/stat.h>
    #include <opencv2/opencv.hpp>void Detector(YOLO_V8*& p) {std::string current_path = ".";std::string imgs_path = current_path + "/images";DIR* dir;struct dirent* ent;if ((dir = opendir(imgs_path.c_str())) != NULL) {while ((ent = readdir(dir)) != NULL) {std::string file_name = ent->d_name;if (file_name.find(".jpg") != std::string::npos || file_name.find(".png") != std::string::npos || file_name.find(".jpeg") != std::string::npos) {std::string img_path = imgs_path + "/" + file_name;cv::Mat img = cv::imread(img_path);std::vector<DL_RESULT> res;p->RunSession(img, res);for (auto& re : res) {cv::RNG rng(cv::getTickCount());cv::Scalar color(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));cv::rectangle(img, re.box, color, 3);float confidence = floor(100 * re.confidence) / 100;std::cout << std::fixed << std::setprecision(2);std::string label = p->classes[re.classId] + " " +std::to_string(confidence).substr(0, std::to_string(confidence).size() - 4);cv::rectangle(img,cv::Point(re.box.x, re.box.y - 25),cv::Point(re.box.x + label.length() * 15, re.box.y),color,cv::FILLED);cv::putText(img,label,cv::Point(re.box.x, re.box.y - 5),cv::FONT_HERSHEY_SIMPLEX,0.75,cv::Scalar(0, 0, 0),2);std::replace(label.begin(), label.end(), '\r', ' ');std::cout << "Target Type:   " << label << std::endl;std::cout << "Loc (x,y,w,h): " << "(" << re.box.x << "," << re.box.y << "," << re.box.width << "," << re.box.height << ")" << std::endl;}cv::Mat resized_img; double scale_factor = 5.0; // 放大倍数 cv::resize(img, resized_img, cv::Size(), scale_factor, scale_factor, cv::INTER_LINEAR); cv::imshow("Result of Detection", resized_img); std::cout << "Press any key to exit" << std::endl;//cv::imshow("Result of Detection", img);cv::waitKey(0);cv::destroyAllWindows();}}closedir(dir);}
    }void Classifier(YOLO_V8*& p) {std::string current_path = ".";std::string imgs_path = current_path;std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<int> dis(0, 255);DIR* dir;struct dirent* ent;if ((dir = opendir(imgs_path.c_str())) != NULL) {while ((ent = readdir(dir)) != NULL) {std::string file_name = ent->d_name;if (file_name.find(".jpg") != std::string::npos || file_name.find(".png") != std::string::npos) {std::string img_path = imgs_path + "/" + file_name;cv::Mat img = cv::imread(img_path);std::vector<DL_RESULT> res;char* ret = p->RunSession(img, res);float positionY = 50;for (int i = 0; i < res.size(); i++) {int r = dis(gen);int g = dis(gen);int b = dis(gen);cv::putText(img, std::to_string(i) + ":", cv::Point(10, positionY), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(b, g, r), 2);cv::putText(img, std::to_string(res.at(i).confidence), cv::Point(70, positionY), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(b, g, r), 2);positionY += 50;}cv::imshow("TEST_CLS", img);cv::waitKey(0);cv::destroyAllWindows();}}closedir(dir);}
    }int ReadPlaneYaml(YOLO_V8*& p) {// Open the YAML filestd::ifstream file("./cfg/plane.yaml");if (!file.is_open()) {std::cerr << "Failed to open file" << std::endl;return 1;}// Read the file line by linestd::string line;std::vector<std::string> lines;while (std::getline(file, line)) {lines.push_back(line);}// Find the start and end of the names sectionstd::size_t start = 0;std::size_t end = 0;for (std::size_t i = 0; i < lines.size(); i++) {if (lines[i].find("names:") != std::string::npos) {start = i + 1;} else if (start > 0 && lines[i].find(':') == std::string::npos) {end = i;break;}}// Extract the namesstd::vector<std::string> names;for (std::size_t i = start; i < end; i++) {std::stringstream ss(lines[i]);std::string name;std::getline(ss, name, ':'); // Extract the number before the delimiterstd::getline(ss, name); // Extract the string after the delimiternames.push_back(name);}p->classes = names;return 0;
    }void DetectTest() {YOLO_V8* yoloDetector = new YOLO_V8;ReadPlaneYaml(yoloDetector);DL_INIT_PARAM params;params.rectConfidenceThreshold = 0.1;params.iouThreshold = 0.5;params.modelPath = "./model/best.onnx";params.imgSize = { 640, 640 };
    #ifdef USE_CUDAparams.cudaEnable = true;// GPU FP32 inferenceparams.modelType = YOLO_DETECT_V8;// GPU FP16 inference//Note: change fp16 onnx model//params.modelType = YOLO_DETECT_V8_HALF;#else// CPU inferenceparams.modelType = YOLO_DETECT_V8;params.cudaEnable = false;#endifyoloDetector->CreateSession(params);Detector(yoloDetector);
    }// void ClsTest() {
    //     YOLO_V8* yoloDetector = new YOLO_V8;
    //     std::string model_path = "cls.onnx";
    //     ReadPlaneYaml(yoloDetector);
    //     DL_INIT_PARAM params{ model_path, YOLO_CLS, {224, 224} };
    //     yoloDetector->CreateSession(params);
    //     Classifier(yoloDetector);
    // }int main() {DetectTest();//ClsTest();
    }
  • 以上代码使用了 dirent.h 中封装的 windows 文件读写方法,如果 vs 报错找不到这个头文件,则自己创建该文件,放置于 vs 安装路径,我这是 D:\Programmer\Microsoft Visual Studio 14.0\VC\include

    /** Dirent interface for Microsoft Visual Studio** Copyright (C) 1998-2019 Toni Ronkko* This file is part of dirent.  Dirent may be freely distributed* under the MIT license.  For all details and documentation, see* https://github.com/tronkko/dirent*/
    #ifndef DIRENT_H
    #define DIRENT_H/* Hide warnings about unreferenced local functions */
    #if defined(__clang__)
    #   pragma clang diagnostic ignored "-Wunused-function"
    #elif defined(_MSC_VER)
    #   pragma warning(disable:4505)
    #elif defined(__GNUC__)
    #   pragma GCC diagnostic ignored "-Wunused-function"
    #endif/** Include windows.h without Windows Sockets 1.1 to prevent conflicts with* Windows Sockets 2.0.*/
    #ifndef WIN32_LEAN_AND_MEAN
    #   define WIN32_LEAN_AND_MEAN
    #endif
    #include <windows.h>#include <stdio.h>
    #include <stdarg.h>
    #include <wchar.h>
    #include <string.h>
    #include <stdlib.h>
    #include <malloc.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>/* Indicates that d_type field is available in dirent structure */
    #define _DIRENT_HAVE_D_TYPE/* Indicates that d_namlen field is available in dirent structure */
    #define _DIRENT_HAVE_D_NAMLEN/* Entries missing from MSVC 6.0 */
    #if !defined(FILE_ATTRIBUTE_DEVICE)
    #   define FILE_ATTRIBUTE_DEVICE 0x40
    #endif/* File type and permission flags for stat(), general mask */
    #if !defined(S_IFMT)
    #   define S_IFMT _S_IFMT
    #endif/* Directory bit */
    #if !defined(S_IFDIR)
    #   define S_IFDIR _S_IFDIR
    #endif/* Character device bit */
    #if !defined(S_IFCHR)
    #   define S_IFCHR _S_IFCHR
    #endif/* Pipe bit */
    #if !defined(S_IFFIFO)
    #   define S_IFFIFO _S_IFFIFO
    #endif/* Regular file bit */
    #if !defined(S_IFREG)
    #   define S_IFREG _S_IFREG
    #endif/* Read permission */
    #if !defined(S_IREAD)
    #   define S_IREAD _S_IREAD
    #endif/* Write permission */
    #if !defined(S_IWRITE)
    #   define S_IWRITE _S_IWRITE
    #endif/* Execute permission */
    #if !defined(S_IEXEC)
    #   define S_IEXEC _S_IEXEC
    #endif/* Pipe */
    #if !defined(S_IFIFO)
    #   define S_IFIFO _S_IFIFO
    #endif/* Block device */
    #if !defined(S_IFBLK)
    #   define S_IFBLK 0
    #endif/* Link */
    #if !defined(S_IFLNK)
    #   define S_IFLNK 0
    #endif/* Socket */
    #if !defined(S_IFSOCK)
    #   define S_IFSOCK 0
    #endif/* Read user permission */
    #if !defined(S_IRUSR)
    #   define S_IRUSR S_IREAD
    #endif/* Write user permission */
    #if !defined(S_IWUSR)
    #   define S_IWUSR S_IWRITE
    #endif/* Execute user permission */
    #if !defined(S_IXUSR)
    #   define S_IXUSR 0
    #endif/* Read group permission */
    #if !defined(S_IRGRP)
    #   define S_IRGRP 0
    #endif/* Write group permission */
    #if !defined(S_IWGRP)
    #   define S_IWGRP 0
    #endif/* Execute group permission */
    #if !defined(S_IXGRP)
    #   define S_IXGRP 0
    #endif/* Read others permission */
    #if !defined(S_IROTH)
    #   define S_IROTH 0
    #endif/* Write others permission */
    #if !defined(S_IWOTH)
    #   define S_IWOTH 0
    #endif/* Execute others permission */
    #if !defined(S_IXOTH)
    #   define S_IXOTH 0
    #endif/* Maximum length of file name */
    #if !defined(PATH_MAX)
    #   define PATH_MAX MAX_PATH
    #endif
    #if !defined(FILENAME_MAX)
    #   define FILENAME_MAX MAX_PATH
    #endif
    #if !defined(NAME_MAX)
    #   define NAME_MAX FILENAME_MAX
    #endif/* File type flags for d_type */
    #define DT_UNKNOWN 0
    #define DT_REG S_IFREG
    #define DT_DIR S_IFDIR
    #define DT_FIFO S_IFIFO
    #define DT_SOCK S_IFSOCK
    #define DT_CHR S_IFCHR
    #define DT_BLK S_IFBLK
    #define DT_LNK S_IFLNK/* Macros for converting between st_mode and d_type */
    #define IFTODT(mode) ((mode) & S_IFMT)
    #define DTTOIF(type) (type)/** File type macros.  Note that block devices, sockets and links cannot be* distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are* only defined for compatibility.  These macros should always return false* on Windows.*/
    #if !defined(S_ISFIFO)
    #   define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO)
    #endif
    #if !defined(S_ISDIR)
    #   define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
    #endif
    #if !defined(S_ISREG)
    #   define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
    #endif
    #if !defined(S_ISLNK)
    #   define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
    #endif
    #if !defined(S_ISSOCK)
    #   define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)
    #endif
    #if !defined(S_ISCHR)
    #   define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR)
    #endif
    #if !defined(S_ISBLK)
    #   define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK)
    #endif/* Return the exact length of the file name without zero terminator */
    #define _D_EXACT_NAMLEN(p) ((p)->d_namlen)/* Return the maximum size of a file name */
    #define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1)#ifdef __cplusplus
    extern "C" {
    #endif/* Wide-character version */
    struct _wdirent {/* Always zero */long d_ino;/* File position within stream */long d_off;/* Structure size */unsigned short d_reclen;/* Length of name without \0 */size_t d_namlen;/* File type */int d_type;/* File name */wchar_t d_name[PATH_MAX+1];
    };
    typedef struct _wdirent _wdirent;struct _WDIR {/* Current directory entry */struct _wdirent ent;/* Private file data */WIN32_FIND_DATAW data;/* True if data is valid */int cached;/* Win32 search handle */HANDLE handle;/* Initial directory name */wchar_t *patt;
    };
    typedef struct _WDIR _WDIR;/* Multi-byte character version */
    struct dirent {/* Always zero */long d_ino;/* File position within stream */long d_off;/* Structure size */unsigned short d_reclen;/* Length of name without \0 */size_t d_namlen;/* File type */int d_type;/* File name */char d_name[PATH_MAX+1];
    };
    typedef struct dirent dirent;struct DIR {struct dirent ent;struct _WDIR *wdirp;
    };
    typedef struct DIR DIR;/* Dirent functions */
    static DIR *opendir (const char *dirname);
    static _WDIR *_wopendir (const wchar_t *dirname);static struct dirent *readdir (DIR *dirp);
    static struct _wdirent *_wreaddir (_WDIR *dirp);static int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
    static int _wreaddir_r(_WDIR *dirp, struct _wdirent *entry, struct _wdirent **result);static int closedir (DIR *dirp);
    static int _wclosedir (_WDIR *dirp);static void rewinddir (DIR* dirp);
    static void _wrewinddir (_WDIR* dirp);static int scandir (const char *dirname, struct dirent ***namelist,int (*filter)(const struct dirent*),int (*compare)(const struct dirent**, const struct dirent**));static int alphasort (const struct dirent **a, const struct dirent **b);static int versionsort (const struct dirent **a, const struct dirent **b);/* For compatibility with Symbian */
    #define wdirent _wdirent
    #define WDIR _WDIR
    #define wopendir _wopendir
    #define wreaddir _wreaddir
    #define wclosedir _wclosedir
    #define wrewinddir _wrewinddir/* Internal utility functions */
    static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp);
    static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp);static int dirent_mbstowcs_s(size_t *pReturnValue,wchar_t *wcstr,size_t sizeInWords,const char *mbstr,size_t count);static int dirent_wcstombs_s(size_t *pReturnValue,char *mbstr,size_t sizeInBytes,const wchar_t *wcstr,size_t count);static void dirent_set_errno (int error);/** Open directory stream DIRNAME for read and return a pointer to the* internal working area that is used to retrieve individual directory* entries.*/
    static _WDIR*
    _wopendir(const wchar_t *dirname)
    {_WDIR *dirp;DWORD n;wchar_t *p;/* Must have directory name */if (dirname == NULL  ||  dirname[0] == '\0') {dirent_set_errno (ENOENT);return NULL;}/* Allocate new _WDIR structure */dirp = (_WDIR*) malloc (sizeof (struct _WDIR));if (!dirp) {return NULL;}/* Reset _WDIR structure */dirp->handle = INVALID_HANDLE_VALUE;dirp->patt = NULL;dirp->cached = 0;/** Compute the length of full path plus zero terminator** Note that on WinRT there's no way to convert relative paths* into absolute paths, so just assume it is an absolute path.*/
    #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)/* Desktop */n = GetFullPathNameW (dirname, 0, NULL, NULL);
    #else/* WinRT */n = wcslen (dirname);
    #endif/* Allocate room for absolute directory name and search pattern */dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16);if (dirp->patt == NULL) {goto exit_closedir;}/** Convert relative directory name to an absolute one.  This* allows rewinddir() to function correctly even when current* working directory is changed between opendir() and rewinddir().** Note that on WinRT there's no way to convert relative paths* into absolute paths, so just assume it is an absolute path.*/
    #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)/* Desktop */n = GetFullPathNameW (dirname, n, dirp->patt, NULL);if (n <= 0) {goto exit_closedir;}
    #else/* WinRT */wcsncpy_s (dirp->patt, n+1, dirname, n);
    #endif/* Append search pattern \* to the directory name */p = dirp->patt + n;switch (p[-1]) {case '\\':case '/':case ':':/* Directory ends in path separator, e.g. c:\temp\ *//*NOP*/;break;default:/* Directory name doesn't end in path separator */*p++ = '\\';}*p++ = '*';*p = '\0';/* Open directory stream and retrieve the first entry */if (!dirent_first (dirp)) {goto exit_closedir;}/* Success */return dirp;/* Failure */
    exit_closedir:_wclosedir (dirp);return NULL;
    }/** Read next directory entry.** Returns pointer to static directory entry which may be overwritten by* subsequent calls to _wreaddir().*/
    static struct _wdirent*
    _wreaddir(_WDIR *dirp)
    {struct _wdirent *entry;/** Read directory entry to buffer.  We can safely ignore the return value* as entry will be set to NULL in case of error.*/(void) _wreaddir_r (dirp, &dirp->ent, &entry);/* Return pointer to statically allocated directory entry */return entry;
    }/** Read next directory entry.** Returns zero on success.  If end of directory stream is reached, then sets* result to NULL and returns zero.*/
    static int
    _wreaddir_r(_WDIR *dirp,struct _wdirent *entry,struct _wdirent **result)
    {WIN32_FIND_DATAW *datap;/* Read next directory entry */datap = dirent_next (dirp);if (datap) {size_t n;DWORD attr;/** Copy file name as wide-character string.  If the file name is too* long to fit in to the destination buffer, then truncate file name* to PATH_MAX characters and zero-terminate the buffer.*/n = 0;while (n < PATH_MAX  &&  datap->cFileName[n] != 0) {entry->d_name[n] = datap->cFileName[n];n++;}entry->d_name[n] = 0;/* Length of file name excluding zero terminator */entry->d_namlen = n;/* File type */attr = datap->dwFileAttributes;if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {entry->d_type = DT_CHR;} else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {entry->d_type = DT_DIR;} else {entry->d_type = DT_REG;}/* Reset dummy fields */entry->d_ino = 0;entry->d_off = 0;entry->d_reclen = sizeof (struct _wdirent);/* Set result address */*result = entry;} else {/* Return NULL to indicate end of directory */*result = NULL;}return /*OK*/0;
    }/** Close directory stream opened by opendir() function.  This invalidates the* DIR structure as well as any directory entry read previously by* _wreaddir().*/
    static int
    _wclosedir(_WDIR *dirp)
    {int ok;if (dirp) {/* Release search handle */if (dirp->handle != INVALID_HANDLE_VALUE) {FindClose (dirp->handle);}/* Release search pattern */free (dirp->patt);/* Release directory structure */free (dirp);ok = /*success*/0;} else {/* Invalid directory stream */dirent_set_errno (EBADF);ok = /*failure*/-1;}return ok;
    }/** Rewind directory stream such that _wreaddir() returns the very first* file name again.*/
    static void
    _wrewinddir(_WDIR* dirp)
    {if (dirp) {/* Release existing search handle */if (dirp->handle != INVALID_HANDLE_VALUE) {FindClose (dirp->handle);}/* Open new search handle */dirent_first (dirp);}
    }/* Get first directory entry (internal) */
    static WIN32_FIND_DATAW*
    dirent_first(_WDIR *dirp)
    {WIN32_FIND_DATAW *datap;DWORD error;/* Open directory and retrieve the first entry */dirp->handle = FindFirstFileExW(dirp->patt, FindExInfoStandard, &dirp->data,FindExSearchNameMatch, NULL, 0);if (dirp->handle != INVALID_HANDLE_VALUE) {/* a directory entry is now waiting in memory */datap = &dirp->data;dirp->cached = 1;} else {/* Failed to open directory: no directory entry in memory */dirp->cached = 0;datap = NULL;/* Set error code */error = GetLastError ();switch (error) {case ERROR_ACCESS_DENIED:/* No read access to directory */dirent_set_errno (EACCES);break;case ERROR_DIRECTORY:/* Directory name is invalid */dirent_set_errno (ENOTDIR);break;case ERROR_PATH_NOT_FOUND:default:/* Cannot find the file */dirent_set_errno (ENOENT);}}return datap;
    }/** Get next directory entry (internal).** Returns*/
    static WIN32_FIND_DATAW*
    dirent_next(_WDIR *dirp)
    {WIN32_FIND_DATAW *p;/* Get next directory entry */if (dirp->cached != 0) {/* A valid directory entry already in memory */p = &dirp->data;dirp->cached = 0;} else if (dirp->handle != INVALID_HANDLE_VALUE) {/* Get the next directory entry from stream */if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) {/* Got a file */p = &dirp->data;} else {/* The very last entry has been processed or an error occurred */FindClose (dirp->handle);dirp->handle = INVALID_HANDLE_VALUE;p = NULL;}} else {/* End of directory stream reached */p = NULL;}return p;
    }/** Open directory stream using plain old C-string.*/
    static DIR*
    opendir(const char *dirname)
    {struct DIR *dirp;/* Must have directory name */if (dirname == NULL  ||  dirname[0] == '\0') {dirent_set_errno (ENOENT);return NULL;}/* Allocate memory for DIR structure */dirp = (DIR*) malloc (sizeof (struct DIR));if (!dirp) {return NULL;}{int error;wchar_t wname[PATH_MAX + 1];size_t n;/* Convert directory name to wide-character string */error = dirent_mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1);if (error) {/** Cannot convert file name to wide-character string.  This* occurs if the string contains invalid multi-byte sequences or* the output buffer is too small to contain the resulting* string.*/goto exit_free;}/* Open directory stream using wide-character name */dirp->wdirp = _wopendir (wname);if (!dirp->wdirp) {goto exit_free;}}/* Success */return dirp;/* Failure */
    exit_free:free (dirp);return NULL;
    }/** Read next directory entry.*/
    static struct dirent*
    readdir(DIR *dirp)
    {struct dirent *entry;/** Read directory entry to buffer.  We can safely ignore the return value* as entry will be set to NULL in case of error.*/(void) readdir_r (dirp, &dirp->ent, &entry);/* Return pointer to statically allocated directory entry */return entry;
    }/** Read next directory entry into called-allocated buffer.** Returns zero on success.  If the end of directory stream is reached, then* sets result to NULL and returns zero.*/
    static int
    readdir_r(DIR *dirp,struct dirent *entry,struct dirent **result)
    {WIN32_FIND_DATAW *datap;/* Read next directory entry */datap = dirent_next (dirp->wdirp);if (datap) {size_t n;int error;/* Attempt to convert file name to multi-byte string */error = dirent_wcstombs_s(&n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1);/** If the file name cannot be represented by a multi-byte string,* then attempt to use old 8+3 file name.  This allows traditional* Unix-code to access some file names despite of unicode* characters, although file names may seem unfamiliar to the user.** Be ware that the code below cannot come up with a short file* name unless the file system provides one.  At least* VirtualBox shared folders fail to do this.*/if (error  &&  datap->cAlternateFileName[0] != '\0') {error = dirent_wcstombs_s(&n, entry->d_name, PATH_MAX + 1,datap->cAlternateFileName, PATH_MAX + 1);}if (!error) {DWORD attr;/* Length of file name excluding zero terminator */entry->d_namlen = n - 1;/* File attributes */attr = datap->dwFileAttributes;if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {entry->d_type = DT_CHR;} else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {entry->d_type = DT_DIR;} else {entry->d_type = DT_REG;}/* Reset dummy fields */entry->d_ino = 0;entry->d_off = 0;entry->d_reclen = sizeof (struct dirent);} else {/** Cannot convert file name to multi-byte string so construct* an erroneous directory entry and return that.  Note that* we cannot return NULL as that would stop the processing* of directory entries completely.*/entry->d_name[0] = '?';entry->d_name[1] = '\0';entry->d_namlen = 1;entry->d_type = DT_UNKNOWN;entry->d_ino = 0;entry->d_off = -1;entry->d_reclen = 0;}/* Return pointer to directory entry */*result = entry;} else {/* No more directory entries */*result = NULL;}return /*OK*/0;
    }/** Close directory stream.*/
    static int
    closedir(DIR *dirp)
    {int ok;if (dirp) {/* Close wide-character directory stream */ok = _wclosedir (dirp->wdirp);dirp->wdirp = NULL;/* Release multi-byte character version */free (dirp);} else {/* Invalid directory stream */dirent_set_errno (EBADF);ok = /*failure*/-1;}return ok;
    }/** Rewind directory stream to beginning.*/
    static void
    rewinddir(DIR* dirp)
    {/* Rewind wide-character string directory stream */_wrewinddir (dirp->wdirp);
    }/** Scan directory for entries.*/
    static int
    scandir(const char *dirname,struct dirent ***namelist,int (*filter)(const struct dirent*),int (*compare)(const struct dirent**, const struct dirent**))
    {struct dirent **files = NULL;size_t size = 0;size_t allocated = 0;const size_t init_size = 1;DIR *dir = NULL;struct dirent *entry;struct dirent *tmp = NULL;size_t i;int result = 0;/* Open directory stream */dir = opendir (dirname);if (dir) {/* Read directory entries to memory */while (1) {/* Enlarge pointer table to make room for another pointer */if (size >= allocated) {void *p;size_t num_entries;/* Compute number of entries in the enlarged pointer table */if (size < init_size) {/* Allocate initial pointer table */num_entries = init_size;} else {/* Double the size */num_entries = size * 2;}/* Allocate first pointer table or enlarge existing table */p = realloc (files, sizeof (void*) * num_entries);if (p != NULL) {/* Got the memory */files = (dirent**) p;allocated = num_entries;} else {/* Out of memory */result = -1;break;}}/* Allocate room for temporary directory entry */if (tmp == NULL) {tmp = (struct dirent*) malloc (sizeof (struct dirent));if (tmp == NULL) {/* Cannot allocate temporary directory entry */result = -1;break;}}/* Read directory entry to temporary area */if (readdir_r (dir, tmp, &entry) == /*OK*/0) {/* Did we get an entry? */if (entry != NULL) {int pass;/* Determine whether to include the entry in result */if (filter) {/* Let the filter function decide */pass = filter (tmp);} else {/* No filter function, include everything */pass = 1;}if (pass) {/* Store the temporary entry to pointer table */files[size++] = tmp;tmp = NULL;/* Keep up with the number of files */result++;}} else {/** End of directory stream reached => sort entries and* exit.*/qsort (files, size, sizeof (void*),(int (*) (const void*, const void*)) compare);break;}} else {/* Error reading directory entry */result = /*Error*/ -1;break;}}} else {/* Cannot open directory */result = /*Error*/ -1;}/* Release temporary directory entry */free (tmp);/* Release allocated memory on error */if (result < 0) {for (i = 0; i < size; i++) {free (files[i]);}free (files);files = NULL;}/* Close directory stream */if (dir) {closedir (dir);}/* Pass pointer table to caller */if (namelist) {*namelist = files;}return result;
    }/* Alphabetical sorting */
    static int
    alphasort(const struct dirent **a, const struct dirent **b)
    {return strcoll ((*a)->d_name, (*b)->d_name);
    }/* Sort versions */
    static int
    versionsort(const struct dirent **a, const struct dirent **b)
    {/* FIXME: implement strverscmp and use that */return alphasort (a, b);
    }/* Convert multi-byte string to wide character string */
    static int
    dirent_mbstowcs_s(size_t *pReturnValue,wchar_t *wcstr,size_t sizeInWords,const char *mbstr,size_t count)
    {int error;#if defined(_MSC_VER)  &&  _MSC_VER >= 1400/* Microsoft Visual Studio 2005 or later */error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count);#else/* Older Visual Studio or non-Microsoft compiler */size_t n;/* Convert to wide-character string (or count characters) */n = mbstowcs (wcstr, mbstr, sizeInWords);if (!wcstr  ||  n < count) {/* Zero-terminate output buffer */if (wcstr  &&  sizeInWords) {if (n >= sizeInWords) {n = sizeInWords - 1;}wcstr[n] = 0;}/* Length of resulting multi-byte string WITH zero terminator */if (pReturnValue) {*pReturnValue = n + 1;}/* Success */error = 0;} else {/* Could not convert string */error = 1;}#endifreturn error;
    }/* Convert wide-character string to multi-byte string */
    static int
    dirent_wcstombs_s(size_t *pReturnValue,char *mbstr,size_t sizeInBytes, /* max size of mbstr */const wchar_t *wcstr,size_t count)
    {int error;#if defined(_MSC_VER)  &&  _MSC_VER >= 1400/* Microsoft Visual Studio 2005 or later */error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count);#else/* Older Visual Studio or non-Microsoft compiler */size_t n;/* Convert to multi-byte string (or count the number of bytes needed) */n = wcstombs (mbstr, wcstr, sizeInBytes);if (!mbstr  ||  n < count) {/* Zero-terminate output buffer */if (mbstr  &&  sizeInBytes) {if (n >= sizeInBytes) {n = sizeInBytes - 1;}mbstr[n] = '\0';}/* Length of resulting multi-bytes string WITH zero-terminator */if (pReturnValue) {*pReturnValue = n + 1;}/* Success */error = 0;} else {/* Cannot convert string */error = 1;}#endifreturn error;
    }/* Set errno variable */
    static void
    dirent_set_errno(int error)
    {
    #if defined(_MSC_VER)  &&  _MSC_VER >= 1400/* Microsoft Visual Studio 2005 and later */_set_errno (error);#else/* Non-Microsoft compiler or older Microsoft compiler */errno = error;#endif
    }#ifdef __cplusplus
    }
    #endif
    #endif /*DIRENT_H*/
  • 注意以上写死了模型路径为 ./model/best.onnx,数据配置文件路径为 ./cfg/plane.yaml,测试图像放在 ./images 路径下。将这些资源放到编译后生成的 C:\Users…\YOLOv8-Test\Test\x64\Release(或Debug) 目录下
    在这里插入图片描述
    现在双击 Test.exe 就能看到 YOLOv8 的识别结果了

  • 最后的问题是,如果在 vscode 里点击运行,还是会报错找不到资源,这是因为工作目录还在根目录,修改属性页->调试 -> 工作目录为 $(OutDir) 即可
    在这里插入图片描述

相关文章:

VS2015 + OpenCV + OnnxRuntime-Cpp + YOLOv8 部署

近期有个工作需求是进行 YOLOv8 模型的 C 部署&#xff0c;部署环境如下 系统&#xff1a;WindowsIDE&#xff1a;VS2015语言&#xff1a;COpenCV 4.5.0OnnxRuntime 1.15.1 0. 预训练模型保存为 .onnx 格式 假设已经有使用 ultralytics 库训练并保存为 .pt 格式的 YOLOv8 模型…...

Notepad++上NppFTP插件的安装和使用教程

一、NppFTP插件下载 图示是已经安装好了插件。 在搜索框里面搜NppFTP&#xff0c;一般情况下&#xff0c;自带的下载地址容易下载失败。这里准备了一个下载连接&#xff1a;Release v0.29.10 ashkulz/NppFTP GitHub 这里我下载的是x86版本 下载好后在nodepad的插件里面选择打…...

Kotlin | Android Provider 的实现案例

目标 使用 Android Room 实现持久化库。 代码 Kotlin 代码编写 DemoDatabase&#xff0c;在build生成 DemoDatabase_Impl 疑问 Provider的数据会存在设备吗&#xff1f; 内部存储: 当使用 Room 创建数据库&#xff08;如 DemoDatabase&#xff09;&#xff0c;数据库文件通常…...

频域自适应空洞卷积FADC详解

定义与原理 在探讨FADC的核心策略之前,我们需要深入了解其定义和工作原理。FADC是一种创新性的卷积技术,旨在克服传统空洞卷积的局限性。其核心思想是从 频谱分析的角度 改进空洞卷积,通过 动态调整膨胀率 来平衡有效带宽和感受野大小。 FADC的工作原理可以从以下几个方面…...

Edge浏览器内置的截长图功能

Edge浏览器内置截图功能 近年来&#xff0c;Edge浏览器不断更新和完善&#xff0c;也提供了长截图功能。在Edge中&#xff0c;只需点击右上角的“...”&#xff0c;然后选择“网页捕获”->“捕获整页”&#xff0c;即可实现长截图。这一功能的简单易用&#xff0c;使其成为…...

GAN的应用

5、GAN的应用 ​ GANs是一个强大的生成模型&#xff0c;它可以使用随机向量生成逼真的样本。我们既不需要知道明确的真实数据分布&#xff0c;也不需要任何数学假设。这些优点使得GANs被广泛应用于图像处理、计算机视觉、序列数据等领域。上图是基于GANs的实际应用场景对不同G…...

Math Reference Notes: 希腊字母表

希腊字母&#xff08;Greek alphabet&#xff09;是古希腊语使用的字母系统&#xff0c;也是西方字母系统的先驱之一&#xff0c;广泛应用于现代数学、物理学、工程学以及各种科学领域。希腊字母有24个字母&#xff0c;它们分为大写和小写两种形式。 1. Alpha (Α, α) 发音&a…...

高通,联发科(MTK)等手机平台调优汇总

一、常见手机型号介绍&#xff1a; ISP除了用在安防行业&#xff0c;还有手机市场&#xff0c;以及目前新型的A/VR眼睛&#xff0c;机器3D视觉机器人&#xff0c;医疗内窥镜这些行业。 下面是一些最近几年发布的,,,旗舰SOC型号&#xff1a; 1.联发科&#xff1a;天玑92…...

Rust语言使用iced实现简单GUI页面

使用cargo新建一个rust项目 cargo new gui_demo cd gui_demo 编辑Cargo.toml文件 ,添加iced依赖 [package] name "gui_demo" version "0.1.0" edition "2021"[dependencies] iced "0.4.2" 编辑src/main.rs文件&#xff1a; u…...

使用wav2vec 2.0进行音位分类任务的研究总结

使用wav2vec 2.0进行音位分类任务的研究总结 原文名称&#xff1a; Using wav2vec 2.0 for phonetic classification tasks: methodological aspects 研究背景 自监督学习在语音中的应用 自监督学习在自动语音识别任务中表现出色&#xff0c;例如说话人识别和验证。变换器模型…...

25/1/11 嵌入式笔记<esp32> 初入esp32

用Arduino平台&#xff0c;学习了点亮led灯。 //定义LED引脚 int led_pin 12&#xff1b;void setup() {//设定引脚为输出模式pinMode(led_pin,OUTPUT):}void loop() {// 点亮LED:digitalWrite(led_pin,HIGH);//延时1sdelay(1000);//熄灭LEDdigitalWrite(led_pin,LOW)://延时…...

基于SMT32U575RIT单片机-中断练习

任务 查看手册对所有的拓展板上和相对应的底板的引脚对应的端口找到以下结论 通过STM32MX软件对各个引脚进行相应的配置 1.第一种切换模式电脑发送 #include "main.h" #include "icache.h" #include "usart.h" #include "gpio.h"/*…...

在Django的Serializer的列表数据中剔除指定元素

【Python工作随笔】 提问 如何在List序列化方法中剔除不要的元素&#xff0c;例如在成绩中剔除0 class BasicDescriptionSubjectBoxPlotSerializer(serializers.Serializer):语文 serializers.ListField(sourcescore_chinese)数学 serializers.ListField(sourcescore_math…...

我喜欢的数学题

偏向抖机灵性质的&#xff0c;考察理解的&#xff0c;而不是比拼计算量的&#xff0c;可能跟现在岁数大了算不明白了多少有点关系吧。 高高手&#xff0c;别太重计算&#xff0c;给普通孩子留条路。就算将来真的理工治国&#xff0c;也没必要都往人形计算机方面引导。毕竟你未来…...

Redis解决热key问题

当Redis遇到热key问题时&#xff0c;即某个或某些key被频繁访问&#xff0c;可能导致单个Redis节点负载过高&#xff0c;影响整个系统性能。以下是一些常见的解决方案&#xff1a; 1. 缓存预热与复制 缓存预热&#xff1a;在系统启动阶段&#xff0c;将热key对应的value预先加…...

【git】-2 分支管理

目录 一、分支的概念 二、查看、创建、切换分支 1、查看分支-git branch 2、创建分支- git branch 分支名 3、切换分支- git checkout 分支名 三、git指针 -实现分支和版本间的切换 四、普通合并分支 git merge 文件名 五、冲突分支合并 ​​​​​​【git】-初始gi…...

Win11+WLS Ubuntu 鸿蒙开发环境搭建(二)

参考文章 penHarmony南向开发笔记&#xff08;一&#xff09;开发环境搭建 OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——标准系统移植指南&#xff08;一&#xff09; OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——小型系统芯片移植指南&#xff08;二&…...

Meilisearch ASP.Net Core API 功能demo

安装 MeiliSearch 0.15.5 0.15.5demo code using Meilisearch; using System.Data; using System.Text.Json; using System.Text.Json.Serialization;namespace MeiliSearchAPI {public class MeilisearchHelper{public MeilisearchHelper(){DefaultClient…...

程序员独立开发竞品分析:确定网站使用什么建站系统

要确定一个网站使用的建站系统&#xff0c;可以通过以下几种方法尝试分析&#xff1a; 查看页面源代码&#xff1a; 打开网站&#xff0c;右键点击页面并选择“查看页面源代码”。在代码中查找一些常见的建站系统标志&#xff0c;例如&#xff1a; WordPress 的迹象&#xff1a…...

selenium+pyqt5自动化工具总结

说明&#xff1a;本工具是&#xff0c;操作外部google浏览器、selenium是无法操作qt界面中嵌套的浏览器的&#xff0c; 工具在后面 1. 代码结构 pycharm打开的文件下&#xff0c;再写一个子文件&#xff0c;文件导入的时候把子文件名带上 这样就可以在 外层使用命令 pyinst…...

docker GPU安装

docker 离线安装 docker下载地址&#xff1a;https://download.docker.com/linux/static/stable/x86_64/ 解压&#xff1a; tar xzvf docker-24.0.6.tgz移动解压后的内容 sudo mv docker/* /usr/local/bin/创建 docker.service配置文件 sudo vim /etc/systemd/system/dock…...

hutool糊涂工具通过注解设置excel宽度

import java.lang.annotation.*;Documented Retention(RetentionPolicy.RUNTIME) Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) public interface ExcelStyle {int width() default 0; }/*** 聊天记录*/ Data public class DialogContentInfo {/**…...

Three.js教程015:全面讲解Three.js的UV与应用

文章目录 全面讲解UV与应用UV介绍代码演示完整代码全面讲解UV与应用 UV介绍 在 Three.js 中,UV 坐标(也称为纹理坐标)是用来定义纹理如何映射到三维模型上的一组二维坐标。UV 坐标的范围通常是 (0, 0) 到 (1, 1),其中: U 对应纹理的横向轴(类似于 X 轴)。V 对应纹理的…...

IOS界面传值-OC

1、页面跳转 由 ViewController 页面跳转至 NextViewController 页面 &#xff08;1&#xff09;ViewController ViewController.h #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend ViewController.m #import "ViewController.h" …...

阿里mod_asr3.0集成webrtc静音算法

alibabacloud-nls-cpp-sdk-master 先到阿里官网下载nls库的源代码&#xff0c;编译生成对应的库文件和头文件。 我编译的放到了以下目录。 /home/jp/2025/alibabacloud-nls-cpp-sdk-master/build/install/NlsSdk3.X_LINUX/include/ /home/jp/2025/alibabacloud-nls-cpp-sdk-…...

[Git] git pull --rebase / git rebase origin/master

1. git pull --rebase 这个命令是用来更新当前分支的&#xff0c;它会从远程仓库拉取更新&#xff0c;然后将你的本地提交重新应用到更新后的基础之上。它相当于先执行 git fetch&#xff0c;然后在当前分支上执行 git rebase origin/。使用 --rebase 而不是默认的 merge 可以…...

Leetcode​​​​​​​3270:求出数字答案

题目描述&#xff1a; 给你三个 正 整数 num1 &#xff0c;num2 和 num3 。 数字 num1 &#xff0c;num2 和 num3 的数字答案 key 是一个四位数&#xff0c;定义如下&#xff1a; 一开始&#xff0c;如果有数字 少于 四位数&#xff0c;给它补 前导 0 。答案 key 的第 i 个数…...

第十一章 施工监测

11 施工监测 11.1 施工监测主要内容、常用仪器与方法 11.1.1 主要内容 1.目的和意义 及时掌握工程自身及周边环境风险动态、通过分析和预测工程结构及周边环境的安全状态与发展趋势&#xff0c;优化调整设计参数和施工参数提供数据支撑。为今后同类工程施工提供类比资料 2…...

Python爬虫应用领域

Python爬虫作为一种强大的数据获取工具&#xff0c;在多个领域发挥着重要作用。以下是Python爬虫在不同领域的应用情况&#xff1a; 一、数据采集与分析 &#xff08;一&#xff09;市场调研 产品信息收集&#xff1a;爬取电商平台的产品详情、价格、销量、用户评价等数据&am…...

软件架构考试基础知识 002:进程的状态与其切换

进程状态转换的说明 在操作系统中&#xff0c;进程的状态表示其当前的执行情况和资源占用情况。进程状态的转换反映了操作系统如何管理和调度进程。以下是进程状态转换的说明&#xff1a; 1. 三态模型&#xff08;Three-state Model&#xff09; 三态模型是最基础的进程状态模…...

政府网站 banner 源码/附近的电脑培训班在哪里

继承 继承是什么&#xff1f; 继承是c的三大特性之一继承。 那么继承有什么作用&#xff1f; 继承可以提高我们代码的复用性。既然是复用&#xff0c;简单来说就是重复利用。 再举一个重复利用的例子ctrlc&#xff0c;ctrlv&#xff0c;也是重复利用&#xff0c;其实复用你…...

苏州微网站建设/企业为何选择网站推广外包?

参考《导弹飞行力学》 对部分参数的解释: dx/dtf(t,x): 之前一直看不懂f&#xff08;t,x&#xff09;到底指的哪个式子&#xff0c;其实在开头就提出来了&#xff0c;f是dy K2△t*f(tk△t/2,xk1/2*K1)&#xff1a;t在导弹飞行力学&#xff0c;是y(0)(M中是1)&#xff0c;所…...

手机网页下载的文件在哪里找/成都网络优化托管公司

2019独角兽企业重金招聘Python工程师标准>>> 欢迎和大家交流技术相关问题&#xff1a; 邮箱: jiangxinnju163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://github.com/jiangxincode 知乎地址: https://www.zhihu.com/people/jiangxinn…...

在某外国网站做代购/上海专业的网络推广

第一种 描述&#xff1a;如果条件许可,把两次请求都放在服务端处理掉一起发回来,这些就在客户端只有一次ajax了 优点&#xff1a;代码放在服务端,安全性比较,且服务端处理速度较快 缺点&#xff1a;可能请求的数据格式是json,这样在服务端处理JSON数据还需要对JSON进行反序列化…...

文章 wordpress/网络平台推广运营有哪些平台

我这个人走得很慢&#xff0c;但是我从不后退。----亚伯拉罕林肯前言&#xff1a; 本周我们主要了解Java中与面向对象的程序设计&#xff08;OOP&#xff09;有关的关键字。后面会逐一详细说明各个关键字的用法。其中特别注意的是this关键字&#xff0c;是Java中最常用的关…...

正规的培训行业网站制作/app推广方案

前言&#xff1a;中秋国庆假期快结束了&#xff0c;马上又要投入到工作或学习中了&#xff0c;可能有部分人已经投入工作或是学习了。 我们在未来会遇到很多的困难与麻烦&#xff0c;但是只要坚信自己不服输不断拼搏&#xff0c;那这些困难与麻烦终究要拜倒在我们脚下。嗒嗒嗒&…...