【无标题】C++使用onnxruntime(使用CPU,不含Nvidia显卡,不用安装cuda)部署yolov10模型并在QT上连接摄像头实时图像采集并进行目标检测
原本是使用halcon进行画出目标区域,再根据区域内的特征点进行模板匹配,但是匹配效果不佳,后续处理可以使用opencv读取视频流,但在该处还是使用了halcon,halcon在QT上显示时需要将 Hobject类型转换为Qimage类型,后面会有几种图像类型的转换函数。该部分检测后画框结果会造成标定框形变,应该是在图形输入时会进行缩放处理以适应模型输入图像的要求,画框在拉伸后的图像上造成畸形框,
前言:使用opencv的DNN模块运行模型时,由于将yolov10模型进行训练后存在TOPK层,opencv最新版opencv4.10.0的DNN模块还没有能够兼容TOPK层(使用https://netron.app该线上工具能够查看模型的网络层情况),解决时删除TOPK层仍旧无法读取模型,还有可能影响到模型的推理结果,由于深度学习,知识掌握不够,不能定位到问题具体位置,搜索解决方案时了解到opencv5.0会兼容TOPK层,或者使用onnxruntime解决该问题,于是搜集资料使用onnxruntime进行模型推理。
1. 该UP主(学不会电磁场的个人空间-学不会电磁场个人主页-哔哩哔哩视频 (bilibili.com))讲解关键点突出,内容简洁:yolov10使用教程,非常详细 01环境配置与训练_哔哩哔哩_bilibili
2.使用opencv进行推理时,直接从官网中OpenCV: YOLO DNNs寻找教程,之后将opencv中的例程(opencv\sources\samples\dnn\yolo_detector.cpp)直接添加到工程文件中修改即可。
OnnxRuntime模型推理
(一)下载对应版本的onnxruntime版本
两种方法:
1. 根据该博客内容,配置过程中个人出现错误(不推荐)如何用visual studio 2019配置OnnxRuntime_onnxruntime.dll-CSDN博客
2. 直接在引用中右键管理Nuget包(推荐Q),搜索onnxruntime安装,安装完成后可以不用链接库,只需要包含头文件。
(二)使用onnxruntime运行模型并画框检测
#include <iostream>
#include <onnxruntime_cxx_api.h>
#include <opencv2/opencv.hpp>
using std::cout;
using std::endl;
using std::vector;
using std::string;
using cv::Mat;
static const vector<std::string> class_name = { "bubble"};//名称应该下载网上模型匹配的//vector
// 前处理
// 推理
// 后处理
// 一条一维向量加形状信息
// 640 * 640 * 3
//
//将图片信息展成一个一维向量,数据类型使用32位浮点型,使用double跑不起来
vector<float> img2vector(const Mat& img)
{
//存储三个通道的值,每个通道都是640*640,展成一维向量vector
vector<float> B;
vector<float> G;
vector<float> R;
//预分配足够的内存空间,减少在添加元素时可能发生的多次内存重新分配。
B.reserve(640 * 640 * 3);
G.reserve(640 * 640);
R.reserve(640 * 640);
//定义指针指向图片数据的首地址
const uchar* pdata = (uchar*)img.datastart;
//每一次需要读取一个像素点的三个数据,每个数据又是一个字节,所以上面的指针使用uchar
for (int i = 0; i < img.dataend - img.datastart; i += 3) // +3 一次读取三个数据
{
// / 255.0 是将像素值从 [0, 255] 范围归一化到 [0.0, 1.0] 范围,减少数据差异,有助于算法收敛
B.push_back((float)*(pdata + i) / 255.0);
G.push_back((float)*(pdata + i + 1) / 255.0);
R.push_back((float)*(pdata + i + 2) / 255.0);
}
//将绿色通道以及红色通道拼接到蓝色通道后面,使之成为一条vector数据
B.insert(B.cend(), G.cbegin(), G.cend());
B.insert(B.cend(), R.cbegin(), R.cend());
return B;
}
//data_num是根据网络图在输出时的显示来决定,Concat下 1*300*6 其中的300,有三百条数据,每一条中有6个数据
void print_float_data(const float* const pdata, int data_num_per_line = 6, int data_num = 300)
{
for (int i = 0; i < data_num; i++)
{
for (int j = 0; j < data_num_per_line; j++)
{
cout << *(pdata + i * data_num_per_line + j) << " ";
}
cout << endl;
}
}
vector<vector<float>> float2vector(const float* const pdata, int data_num_per_line = 6, int data_num = 100, float conf = 0.5)
{
vector<vector<float>> info;
vector<float> info_line;
for (int i = 0; i < data_num; i++)
{
if (*(pdata + i * data_num_per_line + 4) < conf)
{
continue;
}
for (int j = 0; j < data_num_per_line; j++)
{
//如果置信度满足,在该处处理
//cout << *(pdata + i * data_num_per_line + j) << " ";
info_line.push_back(*(pdata + i * data_num_per_line + j));
}
info.push_back(info_line);
info_line.clear();
//cout << endl;
}
return info;
}
void draw_box(Mat& img, const vector<vector<float>>& info)
{
float w = img.cols;
float h = img.rows;
for (int i = 0; i < info.size(); i++)
{
cv::rectangle(img, cv::Point(info[i][0] * w / 640.0, info[i][1] * h / 640.0),
cv::Point(info[i][2] * w / 640.0, info[i][3] * h / 640.0), cv::Scalar(0, 255, 0));
string label;
label += class_name[info[i][5]];
label += " ";
std::stringstream oss;
oss << info[i][4];
label += oss.str();
cv::putText(img, label, cv::Point(info[i][0] * w / 640.0, info[i][1] * h / 640.0), 1, 1, cv::Scalar(0, 255, 0), 2);
}
}
int main()
{
//创建了一个 Ort::Env 实例,它代表推理环境。Ort::Env 是 ONNX Runtime 中用于设置全局线程池和日志级别等的环境对象。
Ort::Env env;
//创建了一个 Ort::Session 实例,它代表一个 ONNX 模型的推理会话。
Ort::Session session(env, L"C:/model/best1.onnx", Ort::SessionOptions{ nullptr });
Mat src_img = cv::imread("C:/pic/1.bmp");
Mat img;
cv::resize(src_img, img, cv::Size(640, 640));
vector<float> img_vector = img2vector(img);
//表示输入图像的形状信息
vector<int64_t> dim = { 1, 3, 640, 640 };
//创建一个输入浮点数(float)类型的张量(tensor)
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU),
img_vector.data(), img_vector.size(),
dim.data(), dim.size()
);
/*
* Ort::Value input_tensor:这是用来存储创建的输入张量的变量。
Ort::Value::CreateTensor<float>:这是创建一个浮点数(float)类型的张量的方法。
Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU):这是一个内存信息对象,指定了张量应该在 CPU 上分配内存。OrtDeviceAllocator 是一个预定义的分配器,用于 CPU 内存分配,OrtMemTypeCPU 指定了内存类型为 CPU。
img_vector.data():这是一个指向输入数据的指针,img_vector 应该是一个包含模型输入数据的 std::vector<float>。
img_vector.size():这是输入数据的大小,即 img_vector 中元素的数量。
dim.data():这是一个指向张量维度信息的指针,dim 应该是一个 std::vector<int64_t>,包含了张量的每个维度的大小。
dim.size():这是张量维度信息的数量,即 dim 中元素的数量。
*/
//指定输入层以及输出层的名称,可以根据网络图中的名称来定义
vector<const char*> input_names = { "images" };
vector<const char*> output_names = { "output0" };
vector<Ort::Value> output_tensors = session.Run(Ort::RunOptions{ nullptr }, input_names.data(), &input_tensor,
input_names.size(), output_names.data(), output_names.size());
/*
vector<Ort::Value> output_tensors:这是一个容器,用于存储模型推理后的输出张量。
session.Run:这是 Ort::Session 类的方法,用于执行模型推理。
Ort::RunOptions{ nullptr }:这是一个运行选项对象,用于传递额外的运行时配置。在这里,使用默认配置,因此传递 nullptr。
input_names.data():这是一个指向输入节点名称数组的指针。input_names 应该是一个 vector<const char*>,包含了模型输入节点的名称。
&input_tensor:这是指向输入张量的指针。这里假设 input_tensor 是之前创建的包含输入数据的 Ort::Value 对象。
input_names.size():这是输入节点的数量。
output_names.data():这是一个指向输出节点名称数组的指针。output_names 应该是一个 vector<const char*>,包含了模型输出节点的名称。
output_names.size():这是输出节点的数量。
*/
//使用浮点数指针获取模型推理结果
float* output = output_tensors[0].GetTensorMutableData<float>();
print_float_data(output); //打印输出
/* xy坐标的最大最小值以及置信度与类别
451.443 520.929 471.047 539.521 0.956787 0
360.653 315.636 375.316 330.225 0.828887 0
331.14 431.965 345.949 444.493 0.813957 0
523.275 375.796 534.163 386.139 0.801398 0
489.376 202.85 508.859 210.283 0.770459 0
517.78 304.954 537.98 322.854 0.705916 0
*/
auto info = float2vector(output);
draw_box(src_img, info);
// 创建一个可调整大小的窗口
const char* window_name = "test";
cv::namedWindow(window_name, cv::WINDOW_NORMAL);
// 在窗口中显示图像
cv::imshow(window_name, src_img);
// 等待用户按键,再继续执行
cv::waitKey(0);
//cv::imshow("test", src_img);
//cv::waitKey(0);
std::cout << "Hello World!\n";
}
该部分检测后画框结果会造成标定框形变,应该是在图形输入时会进行缩放处理以适应模型输入图像的要求,画框在拉伸后的图像上造成畸形框,当后续使用QT显示在label上时,不会产生该问题。
QT上连接USB摄像头实时图像抓取并进行目标检测
由于摄像头采集图像必须连续采集,在主线程中采集图像会造成主线程卡顿,开辟线程专门负责从摄像头采集图像,并定时发送信号将图像传递给图像处理程序,但是后续选择了使用定时器定时采图处理(具体原因忘了)
1.线程的创建
//.h文件
class CameraCaptureThread : public QThread
{
Q_OBJECT
public:
CameraCaptureThread(QObject* parent = nullptr) : QThread(parent) {
// 可以初始化一些线程需要的资源
}
~CameraCaptureThread() {
// 确保线程安全退出
requestInterruption();
wait();
}
protected:
void run();
};
//.cpp文件,线程开启
void CameraCaptureThread::run()
{
在线程开始时初始化定时器
//QTimer timer(this);
//timer.setInterval(500); // 设置定时器间隔为33毫秒
//connect(&timer, &QTimer::timeout, this, &CameraCaptureThread::processImage);
//timer.start();
// Local iconic variables
//HObject ho_Image;
Local control variables
//HTuple hv_AcqHandle;
Image Acquisition 01: Code generated by Image Acquisition 01
//OpenFramegrabber("DirectShow", 1, 1, 0, 0, 0, 0, "default", 8, "rgb", -1, "false",
// "default", "[0] USB Global Camera", 0, -1, &hv_AcqHandle);
//while (0 != 1)
//{
// GrabImage(&ho_Image, hv_AcqHandle);
// //Image Acquisition 01: Do something
//}
//CloseFramegrabber(hv_AcqHandle);
}
//.cpp文件 线程清理
halcon_test::~halcon_test()
{
if (camera_capture_thread && camera_capture_thread->isRunning()) {
camera_capture_thread->requestInterruption();
camera_capture_thread->wait();
}
delete camera_capture_thread; // 清理线程对象
}
2. 定时器采图
//点击开启摄像头按钮,连接线程到相机采集图像 这个按钮与下面的run暂时不起作用,直接由定时器定期访问相机取得图像,进行匹配
void halcon_test::on_btnCameraCtrl_clicked()//摄像头开始采集图像控制
{
//按下开启摄像头,实际是开启定时器,进行定期采图
queryTimer = new QTimer(this);
connect(queryTimer, &QTimer::timeout, this, &halcon_test::processImage);
queryTimer->start(500); // 设置定时器间隔为500毫秒
}
该按钮按下时能够开启定时器定时取图并定时连接到图像处理函数上进行处理
3. halcon中导出cpp程序
halcon进行模板匹配的代码:
主要是两个算子的运用,(1)create_shape_model (2)find_shape_model
read_image (Image, 'D:/气泡1/36.bmp')
* 获取图片的大小
get_image_size(Image, Width, Height)
*关窗口(dev开头的函数,就是设置系统相关的函数)
dev_close_window()
dev_open_window (0, 0, Width, Height, 'black', WindowHandle)
*显示图片
dev_display (Image)
dev_set_color('spring green')
dev_display (Image)
draw_circle (WindowHandle, Row, Column, Radius)
gen_circle (Rectangle, Row, Column, Radius)
dev_display (Image)
reduce_domain (Image, Rectangle, ImageReduced)
* 根据区域创作模板
* 参数一:输入图像
* 参数二:金字塔级别的最大数量。
* 参数三:图案的最小旋转角度
* 参数四:旋转角度的范围
* 参数五:角度的步长(分辨率)
* 参数六:优化的种类和可选使用的方法 用于生成模型。
* 参数七:'use_polarity'表示使用极性信息进行模板匹配。
* 参数八:对比度的阈值
* 参数九:搜索图像中对象的最小对比度。
* 参数十:模型句柄
create_shape_model (ImageReduced, 1, -0.2, 0.2, 'auto', 'auto', 'use_polarity', 'auto', 'auto', ModelID)
* 检查形状模板匹配
* 参数一:输入图像
* 参数二:输入图像的图像金字塔
* 参数三:模型区域金字塔
* 参数四:金字塔级别数。
* 参数五:检查匹配结果的阈值,用于过滤匹配得分低于该阈值的结果,质量参数,影响形状模型图像的生成方式。常用的值范围在 1 到 100 之间,其中 1 表示最低质量,100 表示最高质量。较高的质量值会生成更详细的模型图像,但也可能需要更多的计算时间。
inspect_shape_model (ImageReduced, ModelImages, ModelRegions, 1, 20)
for Index:=1 to 4 by 1
read_image (Image1, 'D:/气泡1/32.bmp')
* find_shape_model 查找对应的模板
* 参数一:输入图像
* 参数二:模型ID(句柄)
* 参数三、四:角度的起始范围和结束范围,以弧度为单位。例如,0 表示无角度搜索,π 表示从 -π/2 到 +π/2 的搜索范围。
* 参数五:参数MinScore定义模板匹配时至少有个什么样的质量系数才算是在图像中找到模板,MinScore设置的越大,搜索的就越快
* 参数六:最小分数,用于筛选匹配结果的最小得分阈值。 期望找到的匹配数量。如果设置为 0,则找到所有可能的匹配。
* 参数七:平移不变性,指定了在匹配中是否考虑平移不变性。允许的最大重叠比例。值在 0 到 1 之间。较小的值会避免找到多个相似的匹配。
* 参数八:亚像素精度,是否启用亚像素级别的精度。
* 参数九:匹配中使用的金字塔级别数
* 参数十:搜索启发式的“贪婪”(0:安全 但速度慢;1:速度快,但可能会错过比赛)。
* 参数十一、十二:保存匹配到的实例的行坐标和列坐标
* 参数十三、十四:保存匹配到的实例的角度信息和得分
*find_shape_model (Image1, ModelID, rad(0), rad(1), 0.5, 0, 0, 'least_squares', 1, 0.5, Row, Column, Angle, Score)
find_shape_model (Image1, ModelID, -0.2, 0.2, 0.35, 0, 0, 'true', 1, 0, Row, Column, Angle, Score)
dev_set_color ('red')
gen_cross_contour_xld (Cross, Row, Column, 25, rad(0))
*Radius1 := [30,30]
*gen_circle_contour_xld (ContCircle, Row, Column, Radius1, 0, 6.28318, 'positive', 1)
*gen_circle (Circle, Row, Column, Radius1)
* 显示找到的轮廓
dev_display_shape_matching_results (ModelID, 'red', Row, Column, Angle, 1, 1, 0)
dev_update_on ()
*输入字体
*set_display_font (WindowHandle, 25 , 'mono', 'true', 'false')
*disp_message (WindowHandle, ['OK'], 'window', 650, 370, 'green', ['white','false'])
stop ()
endfor
* 清除形状模型,释放内存并关闭与该模型相关的所有资源
clear_shape_model (ModelID)
原本是使用halcon进行画出目标区域,再根据区域内的特征点进行模板匹配,但是匹配效果不佳,后续处理可以使用opencv读取视频流,但在该处还是使用了halcon,halcon在QT上显示时需要将 Hobject类型转换为Qimage类型,后面会有几种图像类型的转换函数。
该部分是halcon中导出的cpp程序,其中USB摄像头的类型是halcon软件检测的。
HObject ho_Image;
HTuple hv_AcqHandle;
HTuple pic_SaveAddr;
//Image Acquisition 01: Code generated by Image Acquisition 01
OpenFramegrabber("USB3Vision", 0, 0, 0, 0, 0, 0, "progressive", -1, "default",
-1, "false", "default", "", 0, -1, &hv_AcqHandle);
GrabImageStart(hv_AcqHandle, -1);
GrabImageAsync(&ho_Image, hv_AcqHandle, -1);
CloseFramegrabber(hv_AcqHandle);
保存图像到本地文件:
// 保存图像
QString directory = "C:/image/";
if (directory.isEmpty())
{
// 用户取消选择
return;
}
// 构造文件名
QString fileName_savePic;
// 初始化文件名和自增数字
QString baseFileName = "pic";
int fileIndex = 1; // 起始数字
// 检查文件夹中是否已存在同名文件
do {
fileName_savePic = directory + "/" + baseFileName + QString::number(fileIndex) + ".bmp";
fileIndex++; // 增加文件索引
} while (QFile::exists(fileName_savePic)); // 如果文件已存在,继续循环
pic_SaveAddr = fileName_savePic.toLocal8Bit().toStdString().c_str();
// 保存图像,从halcon中导出该函数不会报错
WriteImage(ho_Image, "bmp", 0, pic_SaveAddr);
// 让用户选择保存文件的目录
QString directory = QFileDialog::getExistingDirectory(this, tr("Choose File"), "C:/");
if (directory.isEmpty())
{
// 用户取消选择
return;
}
// 构造文件名
QString fileName;
// 初始化文件名和自增数字
QString baseFileName = "pic";
int fileIndex = 1; // 起始数字
// 检查文件夹中是否已存在同名文件
do {
fileName = directory + "/" + baseFileName + QString::number(fileIndex) + ".bmp";
fileIndex++; // 增加文件索引
} while (QFile::exists(fileName)); // 如果文件已存在,继续循环
pic_SaveAddr = fileName.toLocal8Bit().toStdString().c_str();
// 保存图像,从halcon中导出该函数不会报错
WriteImage(ho_Image, "bmp", 0, pic_SaveAddr);
使用halcon进行匹配
HTuple hv_Row, hv_Column, hv_Index;
HTuple hv_Angle, hv_Score;
HTuple model_SaveAddr;
HTuple Tem_hv_ModelID;
QString fileName = "C:/bubble/model1.shm"; //路径中不要包含中文,会产生错误
if (fileName.isEmpty())
{
label->setText("file open fail");
return; // 用户取消选择
}
// 检查文件是否存在
if (!QFile::exists(fileName)) {
label->setText("File does not exist: " + fileName);
return;
}
model_SaveAddr = fileName.toLocal8Bit().toStdString().c_str();
ReadShapeModel(model_SaveAddr, &Tem_hv_ModelID);
for (hv_Index = 1; hv_Index <= 4; hv_Index += 1)
{
// 查找模板
FindShapeModel(ho_Image, Tem_hv_ModelID, -0.78, 0.78, 0.8, 0.5, 0.2, "least_squares",
1, 0, &hv_Row, &hv_Column, &hv_Angle, &hv_Score);
// 检查匹配得分是否高于某个阈值,以判断是否匹配成功
if (hv_Score > 0.2) // 假设0.5是一个合适的阈值
{
label->setText("ng");
displayImageOnLabel(label, ho_Image);
setLabelFontAndColor(label2, QColor("red"), 25, true, QFont::Bold);
label2->setText(QString("Capture Count: %1 - ng").arg(captureCount));
//label2->setText("NG");
}
else
{
displayImageOnLabel(label, ho_Image);
setLabelFontAndColor(label2, QColor("green"), 20, true, QFont::Bold);
label2->setText(QString("Capture Count: %1 - ok").arg(captureCount));
//label2->setText("OK");
}
}
几种图片类型间的转换
Hobject转QImage的格式
QImage HObject2QImage(const HalconCpp::HObject& Hobj) //Halcon中的HObject类型转QImage类型
{
HalconCpp::HTuple hv_Chn = HalconCpp::HTuple();
HalconCpp::HTuple hv_Length;
HalconCpp::HTuple hv_Type;
HalconCpp::HObject ho_Img;
QImage image;
ConvertImageType(Hobj, &ho_Img, "byte");
CountChannels(ho_Img, &hv_Chn);
TupleLength(hv_Chn, &hv_Length);
if (hv_Length.L() == 0)
{
return image;
}
HalconCpp::HTuple hv_Width;
HalconCpp::HTuple hv_Height;
int width = 0;
int height = 0;
int step = 0;
if (hv_Chn[0].I() == 1)
{
HalconCpp::HTuple hv_Ptr;
GetImagePointer1(ho_Img, &hv_Ptr, &hv_Type, &hv_Width, &hv_Height);
width = (int)hv_Width;
height = (int)hv_Height;
step = calcBytesPerLine(width, 8);
BYTE* p = (BYTE*)hv_Ptr[0].L(); //必须是L(),不能是I()
QImage temp = QImage(width, height, QImage::Format_Grayscale8);
image = temp.copy();
BYTE* data8 = image.bits();
int pix = 0;
for (int i = 0; i < height; i++)
{
for (int k = 0; k < width; k++)
{
pix = step * i + k;
data8[pix + 0] = p[width * i + k];
}
}
}
else if (hv_Chn[0].I() == 3)
{
HalconCpp::HTuple hv_PtrR, hv_PtrG, hv_PtrB;
GetImagePointer3(ho_Img, &hv_PtrR, &hv_PtrG, &hv_PtrB, &hv_Type, &hv_Width, &hv_Height);
width = (int)hv_Width;
height = (int)hv_Height;
step = calcBytesPerLine(width, 24); //三通道
BYTE* pr = (BYTE*)hv_PtrR[0].L(); //必须是L(),不能是I()
BYTE* pg = (BYTE*)hv_PtrG[0].L();
BYTE* pb = (BYTE*)hv_PtrB[0].L();
QImage temp = QImage(width, height, QImage::Format_RGB888);
image = temp.copy();
BYTE* data24 = image.bits();
int pix = 0;
for (int i = 0; i < height; i++)
{
for (int k = 0; k < width; k++)
{
pix = step * i + k * 3;
data24[pix + 0] = pr[width * i + k]; //QImage格式是RGB,不是BGR
data24[pix + 1] = pg[width * i + k];
data24[pix + 2] = pb[width * i + k];
}
}
}
return image;
}
Hobject转Mat的格式
bool HObject2MatImg(HalconCpp::HObject& Hobj, cv::Mat& matImg)
{
HalconCpp::HTuple htCh;
HalconCpp::HString cType;
HalconCpp::ConvertImageType(Hobj, &Hobj, "byte");
HalconCpp::CountChannels(Hobj, &htCh);
Hlong wid = 0;
Hlong hgt = 0;
if (htCh[0].I() == 1)
{
HalconCpp::HImage hImg(Hobj);
void* ptr = hImg.GetImagePointer1(&cType, &wid, &hgt);
int W = wid;
int H = hgt;
matImg = cv::Mat::zeros(H, W, CV_8UC1);
unsigned char* pdata = static_cast<unsigned char*>(ptr);
memcpy(matImg.data, pdata, W * H);
}
else if (htCh[0].I() == 3)
{
void* Rptr;
void* Gptr;
void* Bptr;
HalconCpp::HImage hImg(Hobj);
hImg.GetImagePointer3(&Rptr, &Gptr, &Bptr, &cType, &wid, &hgt);
int W = wid;
int H = hgt;
matImg = cv::Mat::zeros(H, W, CV_8UC3);
std::vector<cv::Mat> VecM(3);
VecM[0].create(H, W, CV_8UC1);
VecM[1].create(H, W, CV_8UC1);
VecM[2].create(H, W, CV_8UC1);
unsigned char* R = (unsigned char*)Rptr;
unsigned char* G = (unsigned char*)Gptr;
unsigned char* B = (unsigned char*)Bptr;
memcpy(VecM[2].data, R, W * H);
memcpy(VecM[1].data, G, W * H);
memcpy(VecM[0].data, B, W * H);
cv::merge(VecM, matImg);
}
return true;
}
Mat转Hobject的格式
bool Mat2HObject(const cv::Mat& matImg, HalconCpp::HObject& Hobj)
{
// 检查图像是否为空
if (matImg.empty())
{
return false;
}
HalconCpp::HString cType;
Hlong wid = matImg.cols;
Hlong hgt = matImg.rows;
if (matImg.type() == CV_8UC1)
{
// 单通道灰度图像
cType = "byte";
HalconCpp::HImage hImg;
hImg.GenImage1(cType, wid, hgt, matImg.data);
Hobj = hImg;
}
else if (matImg.type() == CV_8UC3)
{
// 三通道彩色图像
cType = "byte";
std::vector<cv::Mat> VecM(3);
cv::split(matImg, VecM);
HalconCpp::HObject R, G, B;
HalconCpp::HImage hImgR, hImgG, hImgB;
hImgR.GenImage1(cType, wid, hgt, VecM[2].data);
hImgG.GenImage1(cType, wid, hgt, VecM[1].data);
hImgB.GenImage1(cType, wid, hgt, VecM[0].data);
R = hImgR;
G = hImgG;
B = hImgB;
HalconCpp::Compose3(R, G, B, &Hobj);
}
else
{
// 不支持的图像类型
return false;
}
return true;
}
欢迎来到由智源人工智能研究院发起的Triton中文社区,这里是一个汇聚了AI开发者、数据科学家、机器学习爱好者以及业界专家的活力平台。我们致力于成为业内领先的Triton技术交流与应用分享的殿堂,为推动人工智能技术的普及与深化应用贡献力量。
更多推荐
所有评论(0)