VisionMaster集成深度学习算法(基础狗)
1.首先要有基础狗,能正常打开VM

2.要有一个模型 不管是yolo还是其他,实在不行去看我上一篇文章 而且模型是onnx格式,如果是英伟达的显卡,也可以tensorRT,个人理解,只要是C++ VS能正常调用起来就行
我后面的例子是 YOLO 分割 + 无监督深度学习两个模型的组合,得到两个模型

3.了解VisionMaster 加入自己模块的方法
3.1 先看下VisionMaster自己的工程
cpp
**VisionMaster4.4.0\Applications\Tools
上面这个路径下存了他们自己的例子
**VisionMaster4.4.0\Applications\Tools\AlgorithmXMLGenerator
这个地方是xml生成器 还有 demo
AlgorithmXMLGenerator.exe
这个插件可以直接生成一堆xml,主要是和VM进行数据交互
也可以生成C++工程 C++工程主要是进行二次开发 或者 进行深度学习推理
3.2 先简单介绍下这个官方软件
第一步:先定义输入输出 比如复选框 输入影像 位置修正信息 / 模型状态 输出图像,这些是为了和上下模块间进行数据交互的

2.定义运行参数 就是我们算法需要提供给外部的接口 调参用

3.生成接口文件 xml 和 C++工程 后面打开开发即可


4.开始详细介绍:
4.1 先准备好自己的深度学习 推理C++调用脚本 这个默认大家是会的
4.2 先介绍下各个xml是代表什么意思
如我新建的工程:
(1) 最主要的是 PatchCoreYoloModuCs.dll 这个是和VM交互的C#程序
在这个路径下的工程查看
cpp
**VisionMaster4.4.0\Applications\Tools\AlgorithmXMLGenerator\project\VM431\ExampleModuCs\ExampleModuCs
(2)PatchCoreYoloModu.dll 这个是自己的C++动态库 推理使用
(3)这些XML文件代表什么意思,从上面这个工程里面的例子可以看出来
cpp
Applications\Module(sp)\x64\UserTools\PatchCoreYoloModu\
├── PatchCoreYoloModu.dll C++ 算法层(必须)
├── PatchCoreYoloModuCs.dll C# 模块句柄
├── PatchCoreYoloModuControl.dll C# 参数面板
├── PatchCoreYoloModu.xml 端口契约(必须)
├── PatchCoreYoloModuAlgorithm.xml 参数存储(必须)
├── PatchCoreYoloModuAlgorithmTab.xml 参数界面(必须)
├── PatchCoreYoloModuDisplay.xml 渲染配置(必须)
├── ToolItemInfo.xml 工具箱条目(必须)
├── PatchCoreYoloModuImageEnable.png 启用图标
├── PatchCoreYoloModu_NormalLogo.png 正常图标
├── PatchCoreYoloModu_StateLogo.png 状态图标
├── App.ini 可选
└── *.pdb 调试用,可留
(4)还有个问题 就是你看下面所有的前缀都是一样的,不知道为什么这样拖到VM中,插件显示不了中文,所以如果对中文要求比较高,所有前面都可以换成中文

(5)编译完的工程可以放到这个路径下
cpp
**VisionMaster4.4.0\Applications\Module(sp)\x64

4.3 开始看C#工程
其实只需要一个就行 注册一下模块名称和参数内容 字段要一致


另外一个是自己写的界面嵌入 这里不介绍 我也不会 dll直接缺省也可以
4.4 看C++工程
common 是VM的第三方库

输出的dll名称要和xml前缀一致 也可以搞成中文

下来就是一堆包含目录和库目录



需要有一个.h 和 .cpp 支持dll导出
下面这四个对外接口是要有的
cpp
int Init();
int Process(IN void* hInput, IN void* hOutput, IN MVDSDK_BASE_MODU_INPUT* modu_input);
int GetParam(IN const char* szParamName, OUT char* pBuff, IN int nBuffSize, OUT int* pDataLen);
int SetParam(IN const char* szParamName, IN const char* pData, IN int nDataLen);
cpp
#pragma once
#ifdef PATCHCOREYOLOMODULE_EXPORTS
#define PATCHCOREYOLO_API __declspec(dllexport)
#else
#define PATCHCOREYOLO_API __declspec(dllimport)
#endif
#include "VmModuleBase.h"
#include "VmAlgModuBase.h"
#include "ErrorCodeDefine.h"
// 你现有工程的推理类,直接复用,不做任何修改
#include "inferencer.h"
#include "TileStitcher.h"
#include "YoloSegInferencer.h"
#include <string>
#include <memory>
/// 输出图类型,和 AlgorithmTab.xml 里 OutputImageType 的 EnumEntry Value 对应
enum OutputImageType
{
OUT_IMG_OVERLAY = 1,
OUT_IMG_HEATMAP = 2,
OUT_IMG_PRED_MASK = 3,
OUT_IMG_YOLO_MASK = 4
};
class PATCHCOREYOLO_API CAlgorithmModule
: public CVmAlgModuleBase
{
public:
explicit CAlgorithmModule();
virtual ~CAlgorithmModule();
int Init();
int Process(IN void* hInput, IN void* hOutput, IN MVDSDK_BASE_MODU_INPUT* modu_input);
int GetParam(IN const char* szParamName, OUT char* pBuff, IN int nBuffSize, OUT int* pDataLen);
int SetParam(IN const char* szParamName, IN const char* pData, IN int nDataLen);
private:
// ---------- 模型管理 ----------
/// 按 m_patchCorePath 重新构造 Inferencer。失败时 m_inferencer 置空并写 m_lastError。
bool ReloadPatchCore();
/// 按 m_yoloPath 重新构造 YoloSegInferencer。
bool ReloadYolo();
/// 用一张全黑图跑一次推理,把 TensorRT/cuDNN 的首次构图开销挪到加载阶段
void WarmUp();
// ---------- 图像转换 ----------
/// HKA_IMAGE -> BGR cv::Mat。只支持 MONO8 和 RGB24(VM 输入端仅产出这两种)。
static bool HkaImageToMat(const HKA_IMAGE& src, cv::Mat& dst);
/// BGR cv::Mat -> 共享内存 + VM 输出。内部会转成 RGB,因为 VM 输出端固定按 RGB 解释。
int OutputMat(void* hOutput, const cv::Mat& mat);
// ---------- 结果统计 ----------
/// 从 pred_mask 数连通域,返回缺陷数,并把总面积写进 totalArea
static int CountDefects(const cv::Mat& predMask, double& totalArea);
private:
// ====== 参数(由 SetParam 从界面收到)======
std::string m_patchCorePath;
std::string m_yoloPath;
int m_tileSize;
int m_overlap;
int m_minMaskPixels;
double m_pixelThreshold;
double m_minArea;
int m_contourWidth;
float m_scoreThreshold;
float m_inferPixelThreshold;
float m_yoloConf;
float m_yoloIou;
int m_outputImageType;
bool m_warmUpEnable;
// ====== 运行时状态 ======
std::unique_ptr<Inferencer> m_inferencer;
std::unique_ptr<YoloSegInferencer> m_yolo;
std::string m_modelStatus; ///< 供 GetParam("ModelStatus") 回给界面
std::string m_execProvider; ///< "GPU" / "CPU" / ""
std::string m_lastError;
// [FIX-OutImage] VM4.4 输出图像必须走 VM_M_SetImageEx 并携带共享内存名;
// 共享块取自输入图像(VM_M_GetImageEx 返回 pImgData + chSharedMemoryName),
// 输出数据 memcpy 进该共享块后用浅拷贝注册,渲染控件只显示共享内存中的图像。
char m_strInShmName[SHARED_MEM_NAME_LEN];
unsigned char* m_pInShmData; ///< 输入共享块的数据指针(输出数据写到这里)
int m_nInShmLen; ///< 输入共享块容量(字节)
};
/////////////////////////// 模块须导出的接口 ///////////////////////////
#ifdef __cplusplus
extern "C" {
#endif
PATCHCOREYOLO_API CAbstractUserModule* __stdcall CreateModule(void* hModule);
PATCHCOREYOLO_API void __stdcall DestroyModule(void* hModule, CAbstractUserModule* pUserModule);
#ifdef __cplusplus
};
#endif
cpp
#include "stdafx.h"
#include "PatchCoreYoloModu.h"
#include "iMVS-6000PixelFormatDefine.h"
#include <stdlib.h>
#include <string.h>
#include <chrono>
// ============================================================================
// 小工具
// ============================================================================
/// UTF-8 / ANSI 窄字符串 -> 宽字符串。Inferencer 的构造函数要 wchar_t*。
/// VM 界面传下来的路径是 UTF-8,含中文目录时必须按 UTF-8 解码,否则模型打不开。
static std::wstring Utf8ToWString(const std::string& s)
{
if (s.empty()) return std::wstring();
int n = ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), NULL, 0);
if (n <= 0)
{
// 不是合法 UTF-8,退回按本地代码页解释
n = ::MultiByteToWideChar(CP_ACP, 0, s.c_str(), (int)s.size(), NULL, 0);
if (n <= 0) return std::wstring();
std::wstring w(n, L'\0');
::MultiByteToWideChar(CP_ACP, 0, s.c_str(), (int)s.size(), &w[0], n);
return w;
}
std::wstring w(n, L'\0');
::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &w[0], n);
return w;
}
/// 把字符串安全拷进 GetParam 的输出缓冲
static int FillOutBuffer(const std::string& src, char* pBuff, int nBuffSize, int* pDataLen)
{
int len = (int)src.size();
if (len + 1 > nBuffSize) len = nBuffSize - 1;
if (len < 0) return IMVS_EC_PARAM;
memcpy(pBuff, src.c_str(), len);
pBuff[len] = '\0';
*pDataLen = len;
return IMVS_EC_OK;
}
// ============================================================================
// 构造 / 析构 / Init
// ============================================================================
CAlgorithmModule::CAlgorithmModule()
: m_tileSize(1024)
, m_overlap(256)
, m_minMaskPixels(5000)
, m_pixelThreshold(0.2)
, m_minArea(800.0)
, m_contourWidth(2)
, m_scoreThreshold(0.5f)
, m_inferPixelThreshold(0.4f)
, m_yoloConf(0.5f)
, m_yoloIou(0.6f)
, m_outputImageType(OUT_IMG_OVERLAY)
, m_warmUpEnable(true)
, m_modelStatus("未加载")
, m_pInShmData(NULL)
, m_nInShmLen(0)
{
m_strInShmName[0] = '\0';
}
CAlgorithmModule::~CAlgorithmModule()
{
}
int CAlgorithmModule::Init()
{
int nRet = VM_M_GetModuleId(m_hModule, &m_nModuleId);
if (IMVS_EC_OK != nRet)
{
m_nModuleId = -1;
return nRet;
}
// 把 PatchCoreYoloModuAlgorithm.xml 里的 DefaultValue 逐个回灌到 SetParam。
// 模型路径此时为空,真正的加载发生在界面选好文件后的那次 SetParam。
nRet = ResetDefaultParam();
if (nRet != IMVS_EC_OK)
{
OutputDebugStringA("###PatchCoreYoloModu: ResetDefaultParam failed.\n");
}
return nRet;
}
// ============================================================================
// 模型加载
// ============================================================================
bool CAlgorithmModule::ReloadPatchCore()
{
m_inferencer.reset();
if (m_patchCorePath.empty())
{
m_modelStatus = "PatchCore 未设置";
return false;
}
try
{
std::wstring wpath = Utf8ToWString(m_patchCorePath);
m_inferencer.reset(new Inferencer(wpath.c_str()));
m_inferencer->InitOnnxEnv();
m_inferencer->setThresholds(m_scoreThreshold, m_inferPixelThreshold);
m_inferencer->setAreaThresholds(m_minArea);
m_execProvider = m_inferencer->isUsingGpu() ? "GPU" : "CPU";
m_modelStatus = "PatchCore 已加载 (" + m_execProvider + ")";
return true;
}
catch (const std::exception& e)
{
m_inferencer.reset();
m_lastError = std::string("PatchCore 加载失败: ") + e.what();
m_modelStatus = m_lastError;
OutputDebugStringA((m_lastError + "\n").c_str());
return false;
}
catch (...)
{
m_inferencer.reset();
m_lastError = "PatchCore 加载失败: 未知异常";
m_modelStatus = m_lastError;
return false;
}
}
bool CAlgorithmModule::ReloadYolo()
{
m_yolo.reset();
if (m_yoloPath.empty())
{
return false;
}
try
{
std::wstring wpath = Utf8ToWString(m_yoloPath);
m_yolo.reset(new YoloSegInferencer(wpath.c_str()));
return true;
}
catch (const std::exception& e)
{
m_yolo.reset();
m_lastError = std::string("YOLO 加载失败: ") + e.what();
m_modelStatus = m_lastError;
OutputDebugStringA((m_lastError + "\n").c_str());
return false;
}
catch (...)
{
m_yolo.reset();
m_lastError = "YOLO 加载失败: 未知异常";
m_modelStatus = m_lastError;
return false;
}
}
void CAlgorithmModule::WarmUp()
{
if (!m_warmUpEnable) return;
try
{
cv::Mat dummy(m_tileSize, m_tileSize, CV_8UC3, cv::Scalar(0, 0, 0));
if (m_inferencer.get()) m_inferencer->Run(dummy, false);
if (m_yolo.get()) m_yolo->Infer(dummy);
}
catch (...)
{
// 预热失败不算致命,第一帧慢一点而已
OutputDebugStringA("###PatchCoreYoloModu: warm-up failed, ignored.\n");
}
}
// ============================================================================
// 参数设置:界面每改一个控件,VM 就调一次这里
// ============================================================================
int CAlgorithmModule::SetParam(IN const char* szParamName, IN const char* pData, IN int nDataLen)
{
if (szParamName == NULL || strlen(szParamName) == 0 || pData == NULL)
{
return IMVS_EC_PARAM;
}
// ---------- 模型路径:变更时立即重新加载 ----------
if (0 == strcmp("PatchCorePath", szParamName))
{
std::string newPath(pData);
if (newPath != m_patchCorePath)
{
m_patchCorePath = newPath;
if (ReloadPatchCore()) WarmUp();
}
}
else if (0 == strcmp("YoloPath", szParamName))
{
std::string newPath(pData);
if (newPath != m_yoloPath)
{
m_yoloPath = newPath;
if (ReloadYolo()) WarmUp();
}
}
// ---------- 分块参数 ----------
else if (0 == strcmp("TileSize", szParamName)) m_tileSize = atoi(pData);
else if (0 == strcmp("Overlap", szParamName)) m_overlap = atoi(pData);
else if (0 == strcmp("MinMaskPixels", szParamName)) m_minMaskPixels = atoi(pData);
// ---------- 后处理参数 ----------
else if (0 == strcmp("PixelThreshold", szParamName)) m_pixelThreshold = atof(pData);
else if (0 == strcmp("ContourWidth", szParamName)) m_contourWidth = atoi(pData);
else if (0 == strcmp("MinArea", szParamName))
{
m_minArea = atof(pData);
// Inferencer 内部也保存了一份面积阈值,保持同步
if (m_inferencer.get()) m_inferencer->setAreaThresholds(m_minArea);
}
// ---------- PatchCore 内部阈值:不用重新加载模型,热更新即可 ----------
else if (0 == strcmp("ScoreThreshold", szParamName))
{
m_scoreThreshold = (float)atof(pData);
if (m_inferencer.get()) m_inferencer->setThresholds(m_scoreThreshold, m_inferPixelThreshold);
}
else if (0 == strcmp("InferPixelThreshold", szParamName))
{
m_inferPixelThreshold = (float)atof(pData);
if (m_inferencer.get()) m_inferencer->setThresholds(m_scoreThreshold, m_inferPixelThreshold);
}
// ---------- YOLO 参数 ----------
else if (0 == strcmp("YoloConf", szParamName)) m_yoloConf = (float)atof(pData);
else if (0 == strcmp("YoloIou", szParamName)) m_yoloIou = (float)atof(pData);
// ---------- 运行选项 ----------
else if (0 == strcmp("OutputImageType", szParamName)) m_outputImageType = atoi(pData);
else if (0 == strcmp("WarmUpEnable", szParamName))
{
m_warmUpEnable = (0 == _stricmp(pData, "True") || 0 == strcmp(pData, "1"));
}
return IMVS_EC_OK;
}
// ============================================================================
// 参数读取:界面打开时回填,以及自定义面板查状态
// ============================================================================
int CAlgorithmModule::GetParam(IN const char* szParamName, OUT char* pBuff,
IN int nBuffSize, OUT int* pDataLen)
{
if (szParamName == NULL || strlen(szParamName) == 0 ||
pBuff == NULL || nBuffSize <= 0 || pDataLen == NULL)
{
return IMVS_EC_PARAM;
}
char tmp[64] = { 0 };
if (0 == strcmp("PatchCorePath", szParamName)) return FillOutBuffer(m_patchCorePath, pBuff, nBuffSize, pDataLen);
else if (0 == strcmp("YoloPath", szParamName)) return FillOutBuffer(m_yoloPath, pBuff, nBuffSize, pDataLen);
else if (0 == strcmp("ModelStatus", szParamName)) return FillOutBuffer(m_modelStatus, pBuff, nBuffSize, pDataLen);
else if (0 == strcmp("ExecProvider", szParamName)) return FillOutBuffer(m_execProvider, pBuff, nBuffSize, pDataLen);
else if (0 == strcmp("TileSize", szParamName)) { sprintf_s(tmp, "%d", m_tileSize); }
else if (0 == strcmp("Overlap", szParamName)) { sprintf_s(tmp, "%d", m_overlap); }
else if (0 == strcmp("MinMaskPixels", szParamName)) { sprintf_s(tmp, "%d", m_minMaskPixels); }
else if (0 == strcmp("ContourWidth", szParamName)) { sprintf_s(tmp, "%d", m_contourWidth); }
else if (0 == strcmp("OutputImageType", szParamName)) { sprintf_s(tmp, "%d", m_outputImageType); }
else if (0 == strcmp("WarmUpEnable", szParamName)) { sprintf_s(tmp, "%s", m_warmUpEnable ? "True" : "False"); }
else if (0 == strcmp("PixelThreshold", szParamName)) { sprintf_s(tmp, "%.4f", m_pixelThreshold); }
else if (0 == strcmp("MinArea", szParamName)) { sprintf_s(tmp, "%.2f", m_minArea); }
else if (0 == strcmp("ScoreThreshold", szParamName)) { sprintf_s(tmp, "%.4f", m_scoreThreshold); }
else if (0 == strcmp("InferPixelThreshold", szParamName)) { sprintf_s(tmp, "%.4f", m_inferPixelThreshold); }
else if (0 == strcmp("YoloConf", szParamName)) { sprintf_s(tmp, "%.4f", m_yoloConf); }
else if (0 == strcmp("YoloIou", szParamName)) { sprintf_s(tmp, "%.4f", m_yoloIou); }
else return IMVS_EC_PARAM;
return FillOutBuffer(std::string(tmp), pBuff, nBuffSize, pDataLen);
}
// ============================================================================
// 图像转换
// ============================================================================
bool CAlgorithmModule::HkaImageToMat(const HKA_IMAGE& src, cv::Mat& dst)
{
if (src.data[0] == NULL || src.width <= 0 || src.height <= 0)
{
return false;
}
// VmModule_GetInputImage 只会产出这两种格式之一(见 VmModule_iMVSFormatToAlgFormat)
switch (src.format)
{
case HKA_IMG_MONO_08:
// 灰度先包一层再转 BGR ------ 两个推理器都按 3 通道设计
{
cv::Mat gray(src.height, src.width, CV_8UC1, src.data[0], src.step[0]);
cv::cvtColor(gray, dst, cv::COLOR_GRAY2BGR);
}
return true;
case HKA_IMG_RGB_RGB24_C3:
// VM 给的是 RGB 排列,OpenCV 全程按 BGR 处理,这里必须换一次通道序
{
cv::Mat rgb(src.height, src.width, CV_8UC3, src.data[0], src.step[0]);
cv::cvtColor(rgb, dst, cv::COLOR_RGB2BGR);
}
return true;
default:
return false;
}
}
static void OutImageDbg(const char* s)
{
OutputDebugStringA(s);
FILE* f = NULL;
if (fopen_s(&f, "C:\\Users\\lixia\\Doubao\\chats\\2026-09-29\\new-chat\\outimage_debug.txt", "a") == 0 && f)
{
fputs(s, f);
fclose(f);
}
}
int CAlgorithmModule::OutputMat(void* hOutput, const cv::Mat& mat)
{
if (mat.empty()) { OutImageDbg("###OutImg: mat EMPTY\n"); return IMVS_EC_PARAM; }
//删掉了----
return nRet;
}
// ============================================================================
// 结果统计
// ============================================================================
int CAlgorithmModule::CountDefects(const cv::Mat& predMask, double& totalArea)
{
totalArea = 0.0;
if (predMask.empty()) return 0;
// TileStitcher 已经按 minArea 过滤过了,这里数的就是最终缺陷数
std::vector<std::vector<cv::Point> > contours;
cv::findContours(predMask.clone(), contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
for (size_t i = 0; i < contours.size(); ++i)
{
totalArea += cv::contourArea(contours[i]);
}
return (int)contours.size();
}
// ============================================================================
// Process:每帧调用
// ============================================================================
int CAlgorithmModule::Process(IN void* hInput, IN void* hOutput,
IN MVDSDK_BASE_MODU_INPUT* modu_input)
{
int nErrCode = IMVS_EC_OK;
m_lastError.clear();
// 先把所有输出置成"无结果",任何一条 return 路径都不会留下上一帧的脏值
VM_M_SetInt (hOutput, "DefectCount", 0, 0);
VM_M_SetFloat(hOutput, "MaxScore", 0, 0.0f);
VM_M_SetFloat(hOutput, "TotalArea", 0, 0.0f);
VM_M_SetInt (hOutput, "MaskPixels", 0, 0);
VM_M_SetInt (hOutput, "TileCount", 0, 0);
VM_M_SetInt (hOutput, "TileSkipped", 0, 0);
VM_M_SetFloat(hOutput, "YoloMs", 0, 0.0f);
VM_M_SetFloat(hOutput, "PredictMs", 0, 0.0f);
VM_M_SetFloat(hOutput, "PostMs", 0, 0.0f);
VM_M_SetFloat(hOutput, "TotalMs", 0, 0.0f);
VM_M_SetString(hOutput, "ErrorMsg", 0, "");
const auto tAll0 = std::chrono::steady_clock::now();
//推理部分删掉了--------------
return nErrCode;
}
// ============================================================================
// 导出接口
// ============================================================================
PATCHCOREYOLO_API CAbstractUserModule* __stdcall CreateModule(void* hModule)
{
assert(hModule != NULL);
CAlgorithmModule* pUserModule = new(std::nothrow) CAlgorithmModule;
if (pUserModule == NULL)
{
return NULL;
}
pUserModule->m_hModule = hModule;
if (IMVS_EC_OK != pUserModule->Init())
{
delete pUserModule;
return NULL;
}
OutputDebugStringA("###PatchCoreYoloModu: CreateModule\n");
return pUserModule;
}
PATCHCOREYOLO_API void __stdcall DestroyModule(void* hModule, CAbstractUserModule* pUserModule)
{
assert(hModule != NULL);
OutputDebugStringA("###PatchCoreYoloModu: DestroyModule\n");
if (pUserModule != NULL)
{
delete pUserModule;
}
}
5 最后将所有动态库拷贝到指定目录就行
看下效果


识别结果

缺陷掩膜

实例分割掩膜
