60、在Visual Studio 2019 环境下,使用C#调用C++生成的dll实现yolov5的图片检测
基本思想:寫了一個簡單的c#調用c++的dll庫圖形化界面程序,完成yolov5檢測圖片的分類,本項目問的人太多了。。。。附屬鏈接吧
鏈接:https://pan.baidu.com/s/1RVh1XtqvFuWuHyuZKxuzDw?pwd=vsxz?
提取碼:vsxz?
--來自百度網盤超級會員V1的分享
一、創建一個c++工程,詳細的構建和配置環境就不詳細累述了,貼代碼吧,導入opencv和ncnn庫即可,因為需要考慮使用C#調用,所以改的代碼盡量迎合C#調用的風格
main.cpp
#include "connect.h"int main(int argc, char** argv) {cv::Mat image = cv::imread("E:\\images\\test\\test1.jpg");unsigned char* src = image.data;cv::Mat result = cv::Mat(image.rows, image.cols, CV_8UC3, src);unsigned char* dest = result.data;const char* model_param = "G:\\ncnn-20210525-windows-vs2019\\ncnn-20210525-windows-vs2019\\x64\\bin\\best_sim211223.param";const char* model_bin = "G:\\ncnn-20210525-windows-vs2019\\ncnn-20210525-windows-vs2019\\x64\\bin\\best_sim211223.bin";ConnectCppWrapper::init_model(model_param, model_bin);ConnectCppWrapper::detect_image(src, dest, image.rows, image.cols);cv::imshow("demo", result);cv::waitKey(0);return 0; }connect.h
#pragma once#include "yolov5.h"namespace ConnectCppWrapper {extern "C" __declspec(dllexport) int __stdcall init_model(const char* model_param, const char* bin_param);extern "C" __declspec(dllexport) int __stdcall detect_image(unsigned char* ImageBuffer, unsigned char* ImageResult, int height, int width);}connect.cpp
#include "connect.h"namespace ConnectCppWrapper {Yolov5* yolov5Item = new Yolov5();int __stdcall init_model(const char* model_param, const char* bin_param){return yolov5Item->init_model(model_param, bin_param);}int __stdcall detect_image(unsigned char* ImageBuffer, unsigned char* ImageResult, int height, int width){cv::Mat result;cv::Mat image = cv::Mat(height, width, CV_8UC3, ImageBuffer);yolov5Item->detect_yolov5(image);int length = (int)(result.total() * result.elemSize());unsigned char* buffer = new unsigned char[length];memcpy(ImageResult, result.data, length * sizeof(unsigned char));return 0;} }yolov5.h? 這部分代碼和模型來自ncnn的example
#pragma once#include "layer.h" #include "net.h"#if defined(USE_NCNN_SIMPLEOCV) #include "simpleocv.h" #else #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #endif #include <float.h> #include <stdio.h> #include <vector>#define YOLOV5_V60 1 //YOLOv5 v6.0 using namespace std; using namespace ncnn; struct Object {cv::Rect_<float> rect;int label;float prob; }; class Yolov5 { public:Yolov5();~Yolov5(); private:float intersection_area(const Object& a, const Object& b);void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right);void qsort_descent_inplace(std::vector<Object>& faceobjects);void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold);void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects);float sigmoid(float x); public:int detect_yolov5(cv::Mat image);void draw_objects(const cv::Mat& image, const std::vector<Object>& objects);int init_model(const char* param_file, const char* bin_file);private:ncnn::Net m_yolov5;const int m_target_size = 640;const float m_prob_threshold = 0.25f;const float m_nms_threshold = 0.45f;};yolov5.cpp
#include "yolov5.h" #if YOLOV5_V60 #define MAX_STRIDE 64 #else #define MAX_STRIDE 32 class YoloV5Focus : public ncnn::Layer { public:YoloV5Focus(){one_blob_only = true;}virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const{int w = bottom_blob.w;int h = bottom_blob.h;int channels = bottom_blob.c;int outw = w / 2;int outh = h / 2;int outc = channels * 4;top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);if (top_blob.empty())return -100;#pragma omp parallel for num_threads(opt.num_threads)for (int p = 0; p < outc; p++){const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);float* outptr = top_blob.channel(p);for (int i = 0; i < outh; i++){for (int j = 0; j < outw; j++){*outptr = *ptr;outptr += 1;ptr += 2;}ptr += w;}}return 0;} };DEFINE_LAYER_CREATOR(YoloV5Focus) #endif //YOLOV5_V60Yolov5::Yolov5() { } Yolov5::~Yolov5() { }float Yolov5::intersection_area(const Object& a, const Object& b) {cv::Rect_<float> inter = a.rect & b.rect;return inter.area(); }void Yolov5::qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right) {int i = left;int j = right;float p = faceobjects[(left + right) / 2].prob;while (i <= j){while (faceobjects[i].prob > p)i++;while (faceobjects[j].prob < p)j--;if (i <= j){// swapstd::swap(faceobjects[i], faceobjects[j]);i++;j--;}}#pragma omp parallel sections{ #pragma omp section{if (left < j) qsort_descent_inplace(faceobjects, left, j);} #pragma omp section{if (i < right) qsort_descent_inplace(faceobjects, i, right);}} }void Yolov5::qsort_descent_inplace(std::vector<Object>& faceobjects) {if (faceobjects.empty())return;qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1); }void Yolov5::nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold) {picked.clear();const int n = faceobjects.size();std::vector<float> areas(n);for (int i = 0; i < n; i++){areas[i] = faceobjects[i].rect.area();}for (int i = 0; i < n; i++){const Object& a = faceobjects[i];int keep = 1;for (int j = 0; j < (int)picked.size(); j++){const Object& b = faceobjects[picked[j]];// intersection over unionfloat inter_area = intersection_area(a, b);float union_area = areas[i] + areas[picked[j]] - inter_area;// float IoU = inter_area / union_areaif (inter_area / union_area > nms_threshold)keep = 0;}if (keep)picked.push_back(i);} }float Yolov5::sigmoid(float x) {return static_cast<float>(1.f / (1.f + exp(-x))); }void Yolov5::generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects) {const int num_grid = feat_blob.h;int num_grid_x;int num_grid_y;if (in_pad.w > in_pad.h){num_grid_x = in_pad.w / stride;num_grid_y = num_grid / num_grid_x;}else{num_grid_y = in_pad.h / stride;num_grid_x = num_grid / num_grid_y;}const int num_class = feat_blob.w - 5;const int num_anchors = anchors.w / 2;for (int q = 0; q < num_anchors; q++){const float anchor_w = anchors[q * 2];const float anchor_h = anchors[q * 2 + 1];const ncnn::Mat feat = feat_blob.channel(q);for (int i = 0; i < num_grid_y; i++){for (int j = 0; j < num_grid_x; j++){const float* featptr = feat.row(i * num_grid_x + j);// find class index with max class scoreint class_index = 0;float class_score = -FLT_MAX;for (int k = 0; k < num_class; k++){float score = featptr[5 + k];if (score > class_score){class_index = k;class_score = score;}}float box_score = featptr[4];float confidence = sigmoid(box_score) * sigmoid(class_score);if (confidence >= prob_threshold){// yolov5/models/yolo.py Detect forward// y = x[i].sigmoid()// y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy// y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # whfloat dx = sigmoid(featptr[0]);float dy = sigmoid(featptr[1]);float dw = sigmoid(featptr[2]);float dh = sigmoid(featptr[3]);float pb_cx = (dx * 2.f - 0.5f + j) * stride;float pb_cy = (dy * 2.f - 0.5f + i) * stride;float pb_w = pow(dw * 2.f, 2) * anchor_w;float pb_h = pow(dh * 2.f, 2) * anchor_h;float x0 = pb_cx - pb_w * 0.5f;float y0 = pb_cy - pb_h * 0.5f;float x1 = pb_cx + pb_w * 0.5f;float y1 = pb_cy + pb_h * 0.5f;Object obj;obj.rect.x = x0;obj.rect.y = y0;obj.rect.width = x1 - x0;obj.rect.height = y1 - y0;obj.label = class_index;obj.prob = confidence;objects.push_back(obj);}}}} }int Yolov5::init_model(const char* param_file, const char* bin_file) {m_yolov5.opt.use_vulkan_compute = true;// yolov5.opt.use_bf16_storage = true;// original pretrained model from https://github.com/ultralytics/yolov5// the ncnn model https://github.com/nihui/ncnn-assets/tree/master/models #if YOLOV5_V60int ok0= m_yolov5.load_param(param_file);int ok1 = m_yolov5.load_model(bin_file); #elseyolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);yolov5.load_param("yolov5s.param");yolov5.load_model("yolov5s.bin"); #endifreturn ok0 + ok1;}int Yolov5::detect_yolov5(cv::Mat image) {std::vector<Object> objects;int img_w = image.cols;int img_h = image.rows;// letterbox pad to multiple of MAX_STRIDEint w = img_w;int h = img_h;float scale = 1.f;if (w > h){scale = (float)m_target_size / w;w = m_target_size;h = h * scale;}else{scale = (float)m_target_size / h;h = m_target_size;w = w * scale;}ncnn::Mat in = ncnn::Mat::from_pixels_resize(image.data, ncnn::Mat::PIXEL_BGR2RGB, img_w, img_h, w, h);// pad to target_size rectangle// yolov5/utils/datasets.py letterboxint wpad = (w + MAX_STRIDE - 1) / MAX_STRIDE * MAX_STRIDE - w;int hpad = (h + MAX_STRIDE - 1) / MAX_STRIDE * MAX_STRIDE - h;ncnn::Mat in_pad;ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);const float norm_vals[3] = { 1 / 255.f, 1 / 255.f, 1 / 255.f };in_pad.substract_mean_normalize(0, norm_vals);ncnn::Extractor ex = m_yolov5.create_extractor();ex.input("images", in_pad);std::vector<Object> proposals;// anchor setting from yolov5/models/yolov5s.yaml// stride 8{ncnn::Mat out;ex.extract("output", out);ncnn::Mat anchors(6);anchors[0] = 10.f;anchors[1] = 13.f;anchors[2] = 16.f;anchors[3] = 30.f;anchors[4] = 33.f;anchors[5] = 23.f;std::vector<Object> objects8;generate_proposals(anchors, 8, in_pad, out, m_prob_threshold, objects8);proposals.insert(proposals.end(), objects8.begin(), objects8.end());}// stride 16{ncnn::Mat out; #if YOLOV5_V60ex.extract("376", out); #elseex.extract("781", out); #endifncnn::Mat anchors(6);anchors[0] = 30.f;anchors[1] = 61.f;anchors[2] = 62.f;anchors[3] = 45.f;anchors[4] = 59.f;anchors[5] = 119.f;std::vector<Object> objects16;generate_proposals(anchors, 16, in_pad, out, m_prob_threshold, objects16);proposals.insert(proposals.end(), objects16.begin(), objects16.end());}// stride 32{ncnn::Mat out; #if YOLOV5_V60ex.extract("401", out); #elseex.extract("801", out); #endifncnn::Mat anchors(6);anchors[0] = 116.f;anchors[1] = 90.f;anchors[2] = 156.f;anchors[3] = 198.f;anchors[4] = 373.f;anchors[5] = 326.f;std::vector<Object> objects32;generate_proposals(anchors, 32, in_pad, out, m_prob_threshold, objects32);proposals.insert(proposals.end(), objects32.begin(), objects32.end());}// sort all proposals by score from highest to lowestqsort_descent_inplace(proposals);// apply nms with nms_thresholdstd::vector<int> picked;nms_sorted_bboxes(proposals, picked, m_nms_threshold);int count = picked.size();objects.resize(count);for (int i = 0; i < count; i++){objects[i] = proposals[picked[i]];// adjust offset to original unpaddedfloat x0 = (objects[i].rect.x - (wpad / 2)) / scale;float y0 = (objects[i].rect.y - (hpad / 2)) / scale;float x1 = (objects[i].rect.x + objects[i].rect.width - (wpad / 2)) / scale;float y1 = (objects[i].rect.y + objects[i].rect.height - (hpad / 2)) / scale;// clipx0 = std::max(std::min(x0, (float)(img_w - 1)), 0.f);y0 = std::max(std::min(y0, (float)(img_h - 1)), 0.f);x1 = std::max(std::min(x1, (float)(img_w - 1)), 0.f);y1 = std::max(std::min(y1, (float)(img_h - 1)), 0.f);objects[i].rect.x = x0;objects[i].rect.y = y0;objects[i].rect.width = x1 - x0;objects[i].rect.height = y1 - y0;}draw_objects(image, objects);return 0;}void Yolov5::draw_objects(const cv::Mat& image, const std::vector<Object>& objects) {static const char* class_names[] = {"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light","fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow","elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee","skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard","tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple","sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch","potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone","microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear","hair drier", "toothbrush"};for (size_t i = 0; i < objects.size(); i++){const Object& obj = objects[i];fprintf(stderr, "%d = %.5f at %.2f %.2f %.2f x %.2f\n", obj.label, obj.prob,obj.rect.x, obj.rect.y, obj.rect.width, obj.rect.height);cv::rectangle(image, obj.rect, cv::Scalar(255, 0, 0));char text[256];sprintf(text, "%s %.1f%%", class_names[obj.label], obj.prob * 100);int baseLine = 0;cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);int x = obj.rect.x;int y = obj.rect.y - label_size.height - baseLine;if (y < 0)y = 0;if (x + label_size.width > image.cols)x = image.cols - label_size.width;cv::rectangle(image, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),cv::Scalar(255, 255, 255), -1);cv::putText(image, text, cv::Point(x, y + label_size.height),cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));}// cv::imshow("demo", image);//cv::waitKey(0);//return image; }先使用vs測試一下
然后再生dll庫
??58、Visual studio 2019+C#傳遞Mat數據給C++動態包處理,并將處理結果Mat返回給C#顯示、保存_sxj731533730-CSDN博客
二、然后在創建.NET工程,拖拽三個按鈕和兩個pictureBox畫布
在已經安裝的模板中選擇編程語言為 --->visualC#,
選擇 ---> "windows經典桌面中的Windows窗體應用"
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; using System.Runtime.InteropServices; using OpenCvSharp; using System.Drawing; using OpenCvSharp.Extensions; using System.Text;namespace WindowsFormsApp1 {static class Program{[DllImport(@"F:\sxj\20211108\detectYolov5Ncnn\x64\Release\detectYolov5Ncnn.dll",CharSet = CharSet.Ansi,CallingConvention = CallingConvention.StdCall)]public static extern int init_model(StringBuilder model_param, StringBuilder model_bin);/// <summary>/// 應用程序的主入口點。/// </summary>[STAThread]static void Main(){StringBuilder model_param = new StringBuilder("F:\\sxj\\20211201\\yolov5s_6.0.param");StringBuilder model_bin = new StringBuilder("F:\\sxj\\20211201\\yolov5s_6.0.bin");init_model(model_param, model_bin);Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new Form1());}} }Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;using OpenCvSharp; using System.Runtime.InteropServices; using OpenCvSharp.Extensions; using System.IO.Compression; using System.Drawing.Imaging;namespace WindowsFormsApp1 {public partial class Form1 : Form{[DllImport(@"F:\sxj\20211108\detectYolov5Ncnn\x64\Release\detectYolov5Ncnn.dll")]public static extern int detect_image(byte[] ImageBuffer, byte[] , int imageHeight, int imageWidth );public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){OpenFileDialog openFileDialog = new OpenFileDialog();openFileDialog.Filter = @"jpeg|*.jpg|bmp|*.bmp|gif|*.gif";if (openFileDialog.ShowDialog() == DialogResult.OK){string fullpath = openFileDialog.FileName;FileStream fs = new FileStream(fullpath, FileMode.Open);byte[] picturebytes = new byte[fs.Length];BinaryReader br = new BinaryReader(fs);picturebytes = br.ReadBytes(Convert.ToInt32(fs.Length));MemoryStream ms = new MemoryStream(picturebytes);Bitmap bmpt = new Bitmap(ms);pictureBox1.Image = bmpt;pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;}else{MessageBox.Show("圖片打開失敗");}}private void button2_Click(object sender, EventArgs e){SaveFileDialog saveImageDialog = new SaveFileDialog();saveImageDialog.Title = "圖片保存";saveImageDialog.Filter = @"jpeg|*.jpg|bmp|*.bmp";saveImageDialog.FileName = System.DateTime.Now.ToString("yyyyMMddHHmmss");//設置默認文件名if (saveImageDialog.ShowDialog() == DialogResult.OK){string fileName = saveImageDialog.FileName.ToString();//Console.WriteLine("fileName" + fileName);if (fileName != "" && fileName != null){string fileExtName = fileName.Substring(fileName.LastIndexOf(".") + 1).ToString();//Console.WriteLine("fileExtName" + fileExtName);System.Drawing.Imaging.ImageFormat imgformat = null;if (fileExtName != ""){switch (fileExtName){case "jpg":imgformat = System.Drawing.Imaging.ImageFormat.Jpeg;break;case "bmp":imgformat = System.Drawing.Imaging.ImageFormat.Bmp;break;default:imgformat = System.Drawing.Imaging.ImageFormat.Jpeg;break;}try{Bitmap bit = new Bitmap(pictureBox2.Image);MessageBox.Show(fileName);pictureBox2.Image.Save(fileName, imgformat);}catch{}}}}}private void button3_Click(object sender, EventArgs e){Bitmap bmp = (Bitmap)pictureBox1.Image.Clone();byte[] source = GetBGRValues(bmp);byte[] dest = source;detect_image(source, dest, bmp.Height, bmp.Width);Bitmap bmpConvert = Byte2Bitmap(dest, bmp.Width, bmp.Height);Image images = bmpConvert;pictureBox2.Image = images;pictureBox2.SizeMode = PictureBoxSizeMode.StretchImage;}public static byte[] GetBGRValues(Bitmap bmp){var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;var imgBytes = bmp.Height * rowBytes;byte[] rgbValues = new byte[imgBytes];IntPtr ptr = bmpData.Scan0;for (var i = 0; i < bmp.Height; i++){Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes);ptr += bmpData.Stride;}bmp.UnlockBits(bmpData);return rgbValues;}public static Bitmap Byte2Bitmap(Byte[] data, int width, int height){Bitmap image = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);BitmapData bmData = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);IntPtr ptr = bmData.Scan0;for (int i = 0; i < image.Height; i++){Marshal.Copy(data, i * image.Width * 3, ptr, image.Width * 3);ptr = (IntPtr)(ptr.ToInt64() + bmData.Stride);}image.UnlockBits(bmData);return image;}private void pictureBox1_Click(object sender, EventArgs e){}} }測試效果圖
(1)初始化界面
(2)選擇一張圖
?
(3)檢測出結果
?
三、如果想把檢測的目標坐標和類別置信度返回給c# 可以這樣寫(目前只寫了一個支持單類檢測的,多類別信息需要變成數組即可)
main.cpp函數
#include "connect.h"int main(int argc, char** argv) {ConnectCppWrapper::DefectResult* data = new ConnectCppWrapper::DefectResult;printf("data.prob= %f\n", data->prob);printf("data.label= %d\n", data->label);printf("data.x= %d\n", data->x);printf("data.y= %d\n", data->y);printf("data.width= %d\n", data->width);printf("data.height= %d\n", data->height);cv::Mat image = cv::imread("F:\\sxj\\predictions.jpg");unsigned char* src = image.data;cv::Mat result = cv::Mat( image.rows,image.cols, CV_8UC3, src);unsigned char* dest = result.data;const char* model_param = "F:\\sxj\\20211201\\yolov5s_6.0.param";const char* model_bin = "F:\\sxj\\20211201\\yolov5s_6.0.bin";ConnectCppWrapper::init_model(model_param, model_bin);ConnectCppWrapper::detect_image(src, dest, image.rows,image.cols, data);printf("data.prob= %f\n", data->prob);printf("data.label= %d\n", data->label);printf("data.x= %d\n", data->x);printf("data.y= %d\n", data->y);printf("data.width= %d\n", data->width);printf("data.height= %d\n", data->height);cv::imshow("demo", result);cv::waitKey(0);return 0; }connect.h頭文件
#pragma once#include "yolov5.h"namespace ConnectCppWrapper {struct DefectResult{int label = 0;float prob = 0;int x = 0;int y = 0;int width = 0;int height = 0;};extern "C" __declspec(dllexport) int __stdcall init_model(const char* model_param, const char* bin_param);extern "C" __declspec(dllexport) int __stdcall detect_image(unsigned char* ImageBuffer, unsigned char* ImageResult,int height, int width, DefectResult * data);}connect.cpp文件
#include "connect.h"namespace ConnectCppWrapper {Yolov5* yolov5Item = new Yolov5();int __stdcall init_model(const char* model_param, const char* bin_param){return yolov5Item->init_model(model_param, bin_param);}int __stdcall detect_image(unsigned char* ImageBuffer, unsigned char* ImageResult, int height,int width, DefectResult* data){cv::Mat result;cv::Mat image = cv::Mat(height,width , CV_8UC3, ImageBuffer);std::vector<Object> objects;int ok = yolov5Item->detect_yolov5(image, objects);if (ok == 0){printf("detect image is successful\n");}for (int i = 0; i < objects.size(); i++){data->label = objects[i].label;data->prob = objects[i].prob;data->x = objects[i].rect.x;data->y = objects[i].rect.x;data->width = objects[i].rect.width;data->height = objects[i].rect.height;printf("%f %f %f %f %d %f\n", objects[i].rect.x, objects[i].rect.y, objects[i].rect.width, objects[i].rect.height, objects[i].label, objects[i].prob);}int length = (int)(result.total() * result.elemSize());unsigned char* buffer = new unsigned char[length];memcpy(ImageResult, result.data, length * sizeof(unsigned char));return 0;} }yolov5.h頭文件
#pragma once#include "layer.h" #include "net.h"#if defined(USE_NCNN_SIMPLEOCV) #include "simpleocv.h" #else #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #endif #include <float.h> #include <stdio.h> #include <vector>#define YOLOV5_V60 1 //YOLOv5 v6.0 using namespace std; using namespace ncnn; struct Object {cv::Rect_<float> rect;int label;float prob; }; class Yolov5 { public:Yolov5();~Yolov5(); private:float intersection_area(const Object& a, const Object& b);void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right);void qsort_descent_inplace(std::vector<Object>& faceobjects);void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold);void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects);float sigmoid(float x); public:int detect_yolov5(cv::Mat image, std::vector<Object>& objects);void draw_objects(const cv::Mat& image, const std::vector<Object>& objects);int init_model(const char* param_file, const char* bin_file);private:ncnn::Net m_yolov5;const int m_target_size = 640;const float m_prob_threshold = 0.25f;const float m_nms_threshold = 0.45f;};yolov5.cpp文件
#include "yolov5.h" #if YOLOV5_V60 #define MAX_STRIDE 64 #else #define MAX_STRIDE 32 class YoloV5Focus : public ncnn::Layer { public:YoloV5Focus(){one_blob_only = true;}virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const{int w = bottom_blob.w;int h = bottom_blob.h;int channels = bottom_blob.c;int outw = w / 2;int outh = h / 2;int outc = channels * 4;top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);if (top_blob.empty())return -100;#pragma omp parallel for num_threads(opt.num_threads)for (int p = 0; p < outc; p++){const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);float* outptr = top_blob.channel(p);for (int i = 0; i < outh; i++){for (int j = 0; j < outw; j++){*outptr = *ptr;outptr += 1;ptr += 2;}ptr += w;}}return 0;} };DEFINE_LAYER_CREATOR(YoloV5Focus) #endif //YOLOV5_V60Yolov5::Yolov5() { } Yolov5::~Yolov5() { }float Yolov5::intersection_area(const Object& a, const Object& b) {cv::Rect_<float> inter = a.rect & b.rect;return inter.area(); }void Yolov5::qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right) {int i = left;int j = right;float p = faceobjects[(left + right) / 2].prob;while (i <= j){while (faceobjects[i].prob > p)i++;while (faceobjects[j].prob < p)j--;if (i <= j){// swapstd::swap(faceobjects[i], faceobjects[j]);i++;j--;}}#pragma omp parallel sections{ #pragma omp section{if (left < j) qsort_descent_inplace(faceobjects, left, j);} #pragma omp section{if (i < right) qsort_descent_inplace(faceobjects, i, right);}} }void Yolov5::qsort_descent_inplace(std::vector<Object>& faceobjects) {if (faceobjects.empty())return;qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1); }void Yolov5::nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold) {picked.clear();const int n = faceobjects.size();std::vector<float> areas(n);for (int i = 0; i < n; i++){areas[i] = faceobjects[i].rect.area();}for (int i = 0; i < n; i++){const Object& a = faceobjects[i];int keep = 1;for (int j = 0; j < (int)picked.size(); j++){const Object& b = faceobjects[picked[j]];// intersection over unionfloat inter_area = intersection_area(a, b);float union_area = areas[i] + areas[picked[j]] - inter_area;// float IoU = inter_area / union_areaif (inter_area / union_area > nms_threshold)keep = 0;}if (keep)picked.push_back(i);} }float Yolov5::sigmoid(float x) {return static_cast<float>(1.f / (1.f + exp(-x))); }void Yolov5::generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects) {const int num_grid = feat_blob.h;int num_grid_x;int num_grid_y;if (in_pad.w > in_pad.h){num_grid_x = in_pad.w / stride;num_grid_y = num_grid / num_grid_x;}else{num_grid_y = in_pad.h / stride;num_grid_x = num_grid / num_grid_y;}const int num_class = feat_blob.w - 5;const int num_anchors = anchors.w / 2;for (int q = 0; q < num_anchors; q++){const float anchor_w = anchors[q * 2];const float anchor_h = anchors[q * 2 + 1];const ncnn::Mat feat = feat_blob.channel(q);for (int i = 0; i < num_grid_y; i++){for (int j = 0; j < num_grid_x; j++){const float* featptr = feat.row(i * num_grid_x + j);// find class index with max class scoreint class_index = 0;float class_score = -FLT_MAX;for (int k = 0; k < num_class; k++){float score = featptr[5 + k];if (score > class_score){class_index = k;class_score = score;}}float box_score = featptr[4];float confidence = sigmoid(box_score) * sigmoid(class_score);if (confidence >= prob_threshold){// yolov5/models/yolo.py Detect forward// y = x[i].sigmoid()// y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy// y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # whfloat dx = sigmoid(featptr[0]);float dy = sigmoid(featptr[1]);float dw = sigmoid(featptr[2]);float dh = sigmoid(featptr[3]);float pb_cx = (dx * 2.f - 0.5f + j) * stride;float pb_cy = (dy * 2.f - 0.5f + i) * stride;float pb_w = pow(dw * 2.f, 2) * anchor_w;float pb_h = pow(dh * 2.f, 2) * anchor_h;float x0 = pb_cx - pb_w * 0.5f;float y0 = pb_cy - pb_h * 0.5f;float x1 = pb_cx + pb_w * 0.5f;float y1 = pb_cy + pb_h * 0.5f;Object obj;obj.rect.x = x0;obj.rect.y = y0;obj.rect.width = x1 - x0;obj.rect.height = y1 - y0;obj.label = class_index;obj.prob = confidence;objects.push_back(obj);}}}} }int Yolov5::init_model(const char* param_file, const char* bin_file) {m_yolov5.opt.use_vulkan_compute = true;// yolov5.opt.use_bf16_storage = true;// original pretrained model from https://github.com/ultralytics/yolov5// the ncnn model https://github.com/nihui/ncnn-assets/tree/master/models #if YOLOV5_V60int ok0 = m_yolov5.load_param(param_file);int ok1 = m_yolov5.load_model(bin_file); #elseyolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);yolov5.load_param("yolov5s.param");yolov5.load_model("yolov5s.bin"); #endifreturn ok0 + ok1; } int Yolov5::detect_yolov5(cv::Mat image, std::vector<Object>& objects) {int img_w = image.cols;int img_h = image.rows;// letterbox pad to multiple of MAX_STRIDEint w = img_w;int h = img_h;float scale = 1.f;if (w > h){scale = (float)m_target_size / w;w = m_target_size;h = h * scale;}else{scale = (float)m_target_size / h;h = m_target_size;w = w * scale;}ncnn::Mat in = ncnn::Mat::from_pixels_resize(image.data, ncnn::Mat::PIXEL_BGR2RGB, img_w, img_h, w, h);// pad to target_size rectangle// yolov5/utils/datasets.py letterboxint wpad = (w + MAX_STRIDE - 1) / MAX_STRIDE * MAX_STRIDE - w;int hpad = (h + MAX_STRIDE - 1) / MAX_STRIDE * MAX_STRIDE - h;ncnn::Mat in_pad;ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);const float norm_vals[3] = { 1 / 255.f, 1 / 255.f, 1 / 255.f };in_pad.substract_mean_normalize(0, norm_vals);ncnn::Extractor ex = m_yolov5.create_extractor();ex.input("images", in_pad);std::vector<Object> proposals;// anchor setting from yolov5/models/yolov5s.yaml// stride 8{ncnn::Mat out;ex.extract("output", out);ncnn::Mat anchors(6);anchors[0] = 10.f;anchors[1] = 13.f;anchors[2] = 16.f;anchors[3] = 30.f;anchors[4] = 33.f;anchors[5] = 23.f;std::vector<Object> objects8;generate_proposals(anchors, 8, in_pad, out, m_prob_threshold, objects8);proposals.insert(proposals.end(), objects8.begin(), objects8.end());}// stride 16{ncnn::Mat out; #if YOLOV5_V60ex.extract("376", out); #elseex.extract("781", out); #endifncnn::Mat anchors(6);anchors[0] = 30.f;anchors[1] = 61.f;anchors[2] = 62.f;anchors[3] = 45.f;anchors[4] = 59.f;anchors[5] = 119.f;std::vector<Object> objects16;generate_proposals(anchors, 16, in_pad, out, m_prob_threshold, objects16);proposals.insert(proposals.end(), objects16.begin(), objects16.end());}// stride 32{ncnn::Mat out; #if YOLOV5_V60ex.extract("401", out); #elseex.extract("801", out); #endifncnn::Mat anchors(6);anchors[0] = 116.f;anchors[1] = 90.f;anchors[2] = 156.f;anchors[3] = 198.f;anchors[4] = 373.f;anchors[5] = 326.f;std::vector<Object> objects32;generate_proposals(anchors, 32, in_pad, out, m_prob_threshold, objects32);proposals.insert(proposals.end(), objects32.begin(), objects32.end());}// sort all proposals by score from highest to lowestqsort_descent_inplace(proposals);// apply nms with nms_thresholdstd::vector<int> picked;nms_sorted_bboxes(proposals, picked, m_nms_threshold);int count = picked.size();objects.resize(count);for (int i = 0; i < count; i++){objects[i] = proposals[picked[i]];// adjust offset to original unpaddedfloat x0 = (objects[i].rect.x - (wpad / 2)) / scale;float y0 = (objects[i].rect.y - (hpad / 2)) / scale;float x1 = (objects[i].rect.x + objects[i].rect.width - (wpad / 2)) / scale;float y1 = (objects[i].rect.y + objects[i].rect.height - (hpad / 2)) / scale;// clipx0 = std::max(std::min(x0, (float)(img_w - 1)), 0.f);y0 = std::max(std::min(y0, (float)(img_h - 1)), 0.f);x1 = std::max(std::min(x1, (float)(img_w - 1)), 0.f);y1 = std::max(std::min(y1, (float)(img_h - 1)), 0.f);objects[i].rect.x = x0;objects[i].rect.y = y0;objects[i].rect.width = x1 - x0;objects[i].rect.height = y1 - y0;}draw_objects(image, objects);return 0;}void Yolov5::draw_objects(const cv::Mat& image, const std::vector<Object>& objects) {static const char* class_names[] = {"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light","fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow","elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee","skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard","tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple","sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch","potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone","microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear","hair drier", "toothbrush"};for (size_t i = 0; i < objects.size(); i++){const Object& obj = objects[i];fprintf(stderr, "%d = %.5f at %.2f %.2f %.2f x %.2f\n", obj.label, obj.prob,obj.rect.x, obj.rect.y, obj.rect.width, obj.rect.height);cv::rectangle(image, obj.rect, cv::Scalar(255, 0, 0));char text[256];sprintf(text, "%s %.1f%%", class_names[obj.label], obj.prob * 100);int baseLine = 0;cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);int x = obj.rect.x;int y = obj.rect.y - label_size.height - baseLine;if (y < 0)y = 0;if (x + label_size.width > image.cols)x = image.cols - label_size.width;cv::rectangle(image, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),cv::Scalar(255, 255, 255), -1);cv::putText(image, text, cv::Point(x, y + label_size.height),cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));}// cv::imshow("demo", image);//cv::waitKey(0);//return image; }同時將構建的工程設置一下
?先測試一下生成exe是沒有問題的,然后在生成dll動態庫,去修改一下c#的代碼,進行聯調即可
c#的工程也需要設置一下
c# Program.cs文件內容不變
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; using System.Runtime.InteropServices; using OpenCvSharp; using System.Drawing; using OpenCvSharp.Extensions; using System.Text;namespace WindowsFormsApp1 {static class Program{[DllImport(@"F:\sxj\20211108\detectYolov5Ncnn\x64\Release\detectYolov5Ncnn.dll",CharSet = CharSet.Ansi,CallingConvention = CallingConvention.StdCall)]public static extern int init_model(StringBuilder model_param, StringBuilder model_bin);/// <summary>/// 應用程序的主入口點。/// </summary>[STAThread]static void Main(){StringBuilder model_param = new StringBuilder("F:\\sxj\\20211201\\yolov5s_6.0.param");StringBuilder model_bin = new StringBuilder("F:\\sxj\\20211201\\yolov5s_6.0.bin");init_model(model_param, model_bin);Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new Form1());}} }Form1.cs文件修改一下,寫了個只支持單檢測類,好像很復雜 c#和c++ 進行托管內存和非托管內存傳遞,看的msdn有點復雜
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;using OpenCvSharp; using System.Runtime.InteropServices; using OpenCvSharp.Extensions; using System.IO.Compression; using System.Drawing.Imaging;namespace WindowsFormsApp1 {public partial class Form1 : Form{[DllImport(@"F:\sxj\20211108\detectYolov5Ncnn\x64\Release\detectYolov5Ncnn.dll", CallingConvention = CallingConvention.StdCall)]public static extern int detect_image(byte[] ImageBuffer, byte[] ImageResult, int imageHeight, int imageWidth , IntPtr ptrItem);[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]public struct DefectResult{public int label;public float prob;public int x;public int y;public int width;public int height;}public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){OpenFileDialog openFileDialog = new OpenFileDialog();openFileDialog.Filter = @"jpeg|*.jpg|bmp|*.bmp|gif|*.gif";if (openFileDialog.ShowDialog() == DialogResult.OK){string fullpath = openFileDialog.FileName;FileStream fs = new FileStream(fullpath, FileMode.Open);byte[] picturebytes = new byte[fs.Length];BinaryReader br = new BinaryReader(fs);picturebytes = br.ReadBytes(Convert.ToInt32(fs.Length));MemoryStream ms = new MemoryStream(picturebytes);Bitmap bmpt = new Bitmap(ms);pictureBox1.Image = bmpt;pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;}else{MessageBox.Show("圖片打開失敗");}}private void button2_Click(object sender, EventArgs e){SaveFileDialog saveImageDialog = new SaveFileDialog();saveImageDialog.Title = "圖片保存";saveImageDialog.Filter = @"jpeg|*.jpg|bmp|*.bmp";saveImageDialog.FileName = System.DateTime.Now.ToString("yyyyMMddHHmmss");//設置默認文件名if (saveImageDialog.ShowDialog() == DialogResult.OK){string fileName = saveImageDialog.FileName.ToString();//Console.WriteLine("fileName" + fileName);if (fileName != "" && fileName != null){string fileExtName = fileName.Substring(fileName.LastIndexOf(".") + 1).ToString();//Console.WriteLine("fileExtName" + fileExtName);System.Drawing.Imaging.ImageFormat imgformat = null;if (fileExtName != ""){switch (fileExtName){case "jpg":imgformat = System.Drawing.Imaging.ImageFormat.Jpeg;break;case "bmp":imgformat = System.Drawing.Imaging.ImageFormat.Bmp;break;default:imgformat = System.Drawing.Imaging.ImageFormat.Jpeg;break;}try{Bitmap bit = new Bitmap(pictureBox2.Image);MessageBox.Show(fileName);pictureBox2.Image.Save(fileName, imgformat);}catch{}}}}}private void button3_Click(object sender, EventArgs e){int workStationCount = 1;int size = Marshal.SizeOf(typeof(DefectResult)) * workStationCount;byte[] bytes = new byte[size];IntPtr infosIntptr = Marshal.AllocHGlobal(size);DefectResult[] pClass = new DefectResult[workStationCount];Bitmap bmp = (Bitmap)pictureBox1.Image.Clone();byte[] source = GetBGRValues(bmp);byte[] dest = source;detect_image(source, dest, bmp.Height, bmp.Width, infosIntptr);string[] class_names = new string[]{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light","fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow","elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee","skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard","tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple","sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch","potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone","microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear","hair drier", "toothbrush"};for (int inkIndex = 0; inkIndex < workStationCount; inkIndex++){IntPtr pPonitor = new IntPtr(infosIntptr.ToInt64() + Marshal.SizeOf(typeof(DefectResult)) * inkIndex);pClass[inkIndex] = (DefectResult)Marshal.PtrToStructure(pPonitor, typeof(DefectResult));Console.WriteLine("{0} ", pClass[inkIndex].prob);Console.WriteLine("{0} ", class_names[pClass[inkIndex].label]);Console.WriteLine("{0} ", pClass[inkIndex].x);Console.WriteLine("{0} ", pClass[inkIndex].y);Console.WriteLine("{0} ", pClass[inkIndex].width);Console.WriteLine("{0} ", pClass[inkIndex].height);}Marshal.FreeHGlobal(infosIntptr);Bitmap bmpConvert = Byte2Bitmap(dest, bmp.Width, bmp.Height);Image images = bmpConvert;pictureBox2.Image = images;pictureBox2.SizeMode = PictureBoxSizeMode.StretchImage;}public static byte[] GetBGRValues(Bitmap bmp){var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;var imgBytes = bmp.Height * rowBytes;byte[] rgbValues = new byte[imgBytes];IntPtr ptr = bmpData.Scan0;for (var i = 0; i < bmp.Height; i++){Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes);ptr += bmpData.Stride;}bmp.UnlockBits(bmpData);return rgbValues;}public static Bitmap Byte2Bitmap(Byte[] data, int width, int height){Bitmap image = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);BitmapData bmData = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);IntPtr ptr = bmData.Scan0;for (int i = 0; i < image.Height; i++){Marshal.Copy(data, i * image.Width * 3, ptr, image.Width * 3);ptr = (IntPtr)(ptr.ToInt64() + bmData.Stride);}image.UnlockBits(bmData);return image;}private void pictureBox1_Click(object sender, EventArgs e){}} }然后使用debug模式或者直接release模式輸出一下 就可看到 在c++ 中完成了繪圖功能,也可以把類別信息返回了
附錄一下debug調試結果
還是那位可愛的小姐姐
注意 c#中的圖片高 寬的參數與 c++的高寬 對應一致~~~
如果需要從c++的dll傳遞變長的數據結構給c#,可以使用在c++ 使用rapidjson組成字符串,然后賦值給char 數組,然后在c#序列化成json就可以完成多目標數據遞送
48、OAK通過共享內存傳遞變長結構體(Rapidjson)進行數據和圖片交互_天晝AI實驗室的博客-CSDN博客
總結
以上是生活随笔為你收集整理的60、在Visual Studio 2019 环境下,使用C#调用C++生成的dll实现yolov5的图片检测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简易PROTUES的定时器仿真
- 下一篇: 一文掌握面向Windows平台的深度学习