VS2015 + OpenCV + OnnxRuntime-Cpp + YOLOv8 部署
- 近期有个工作需求是进行 YOLOv8 模型的 C++ 部署,部署环境如下
- 系统:Windows
- IDE:VS2015
- 语言:C++
- OpenCV 4.5.0
- 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 - 提取的文件中,以下是我们之后需要的
- 头文件:C:\Users…\opencv\build\include\opencv2
- 动态库:C:\Users…\opencv\build\x64\vc14\bin\opencv_world450.dll
- 静态库: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,下载后直接解压。
- 提取的文件中,以下是我们之后需要的
- 头文件:C:\Users…\onnxruntime-win-x64-1.15.1\include
- 动态库:C:\Users…\onnxruntime-win-x64-1.15.1\lib 下的所有 .dll 文件
- 静态库: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 文件夹
- 在 bin 中粘贴 1.2 和 1.3 节提到的所有动态库文件
- 把源码粘贴到根目录,并添加到项目中
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 file std::ifstream file("./cfg/plane.yaml"); if (!file.is_open()) { std::cerr << "Failed to open file" << std::endl; return 1; } // Read the file line by line std::string line; std::vector<std::string> lines; while (std::getline(file, line)) { lines.push_back(line); } // Find the start and end of the names section std::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 names std::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 delimiter std::getline(ss, name); // Extract the string after the delimiter names.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_CUDA params.cudaEnable = true; // GPU FP32 inference params.modelType = YOLO_DETECT_V8; // GPU FP16 inference //Note: change fp16 onnx model //params.modelType = YOLO_DETECT_V8_HALF; #else // CPU inference params.modelType = YOLO_DETECT_V8; params.cudaEnable = false; #endif yoloDetector->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; } #endif return 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; } #endif return 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) 即可