手把手教你用c#制作一個(gè)小型桌面程序
封裝dll
首先使用visual stdio 創(chuàng)建Dll新項(xiàng)目,然后屬性管理器導(dǎo)入自己的工程屬性表(如果沒(méi)有可以參考visual stdio 如何配置opencv等其他環(huán)境)
創(chuàng)建完成后 系統(tǒng)會(huì)自動(dòng)生成一些文件,其中 pch.cpp 先不要修改,pch.h中先導(dǎo)入自己需要用到的庫(kù),下面是我的代碼
pch.h
#pragma once #include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> #include <iostream> #include <string>
現(xiàn)在編寫我們的接口代碼,我封裝的是resnet18的代碼:
首先添加源文件ResNetDll.cpp:
ResNetDll.cpp
#include "pch.h"
#include "ResNetDll.h"
// 全局變量,用于存儲(chǔ)模型路徑和圖像路徑
static std::string g_imagePath;
static std::string g_modelPath;
// 圖像預(yù)處理函數(shù)
cv::Mat transformImage(const std::string& imagePath) {
cv::Mat image = cv::imread(imagePath);
if (image.empty()) {
throw std::runtime_error("Failed to load image.");
}
cv::Mat resizedImage;
cv::resize(image, resizedImage, cv::Size(224, 224));
cv::Mat floatImage;
resizedImage.convertTo(floatImage, CV_32F, 1.0 / 255.0);
cv::Mat normalizedImage;
cv::Scalar mean(0.485, 0.456, 0.406);
cv::Scalar stdDev(0.229, 0.224, 0.225);
cv::subtract(floatImage, mean, normalizedImage);
cv::divide(normalizedImage, stdDev, normalizedImage);
// 從 BGR 轉(zhuǎn)換到 RGB
cv::Mat rgbImage;
cv::cvtColor(normalizedImage, rgbImage, cv::COLOR_BGR2RGB);
return rgbImage;
}
// 推理函數(shù)
const char* run_inference() {
static std::string result;
try {
// 加載 ONNX 模型
cv::dnn::Net net = cv::dnn::readNetFromONNX(g_modelPath);
if (net.empty()) {
result = "Failed to load the model.";
return result.c_str();
}
// 預(yù)處理圖像
cv::Mat rgbImage = transformImage(g_imagePath);
// 創(chuàng)建 blob 并設(shè)置為網(wǎng)絡(luò)輸入
cv::Mat blob = cv::dnn::blobFromImage(rgbImage, 1.0, cv::Size(224, 224), cv::Scalar(), true, false);
net.setInput(blob);
// 執(zhí)行推理
cv::Mat output = net.forward();
// 處理輸出
cv::Mat prob = output.reshape(1, 1); // 變換成 1D 張量
cv::Point classIdPoint;
double confidence;
// 用來(lái)找到矩陣或圖像中元素的最小值和最大值,以及它們所在的位置
cv::minMaxLoc(prob, 0, &confidence, 0, &classIdPoint);
int classId = classIdPoint.x;
// 根據(jù)預(yù)測(cè)結(jié)果返回相應(yīng)的標(biāo)簽
result = "Predicted Class ID: " + std::to_string(classId) + " with confidence: " + std::to_string(confidence);
return result.c_str();
}
catch (const std::exception& e) {
result = "Error occurred during inference: " + std::string(e.what());
return result.c_str();
}
}
// DLL 暴露的函數(shù),用于設(shè)置圖像路徑
extern "C" RESNETDLL_API void set_image_path(const char* imagePath) {
g_imagePath = imagePath;
}
// DLL 暴露的函數(shù),用于設(shè)置模型路徑
extern "C" RESNETDLL_API void set_model_path(const char* modelPath) {
g_modelPath = modelPath;
}
// DLL 暴露的函數(shù),運(yùn)行推理
extern "C" RESNETDLL_API const char* run_resnet() {
return run_inference();
}
ResNetDll.h:
#pragma once
#ifdef RESNETDLL_EXPORTS
#define RESNETDLL_API __declspec(dllexport)
#else
#define RESNETDLL_API __declspec(dllimport)
#endif
extern "C" {
// 設(shè)置圖像路徑
RESNETDLL_API void set_image_path(const char* imagePath);
// 設(shè)置模型路徑
RESNETDLL_API void set_model_path(const char* modelPath);
// 運(yùn)行推理
RESNETDLL_API const char* run_resnet();
}點(diǎn)擊生成dll,就封裝成了windows動(dòng)態(tài)庫(kù)
制作Demo
創(chuàng)建.NET Framework新項(xiàng)目,將之前生成的dll放在Demo文件夾的bin ->debug或是 release中(看你自己用的什么模式),
新建NativeMethods.cs 這個(gè)文件用于 導(dǎo)入 dll中的接口函數(shù)或類
我的代碼如下
NativeMethods.cs
using System;
using System.Runtime.InteropServices;
namespace ResNetApp
{
public static class NativeMethods
{
// 導(dǎo)入 DLL 中的 set_image_path 函數(shù)
[DllImport("ResNetDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void set_image_path(string imagePath);
// 導(dǎo)入 DLL 中的 set_model_path 函數(shù)
[DllImport("ResNetDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void set_model_path(string modelPath);
// 導(dǎo)入 DLL 中的 run_resnet 函數(shù)
[DllImport("ResNetDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr run_resnet();
}
}然后在窗口中拉入你想要的控件,這是我的窗口布局

布局完了之后會(huì)自動(dòng)生成Form1.Designer.cs 的窗口設(shè)計(jì)代碼,點(diǎn)擊控件按F4 還可以修改他們的屬性
Form1.cs
這個(gè)代碼 編寫你想要每個(gè)控件實(shí)現(xiàn)的功能:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ResNetApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonSelectImage_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "圖像文件|*.bmp;*.jpg;*.jpeg;*.png";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
textBoxImagePath.Text = openFileDialog.FileName; // 顯示選擇的圖像路徑
}
}
private void buttonSelectModel_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "ONNX 模型文件|*.onnx";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
textBoxModelPath.Text = openFileDialog.FileName; // 顯示選擇的模型路徑
}
}
private void button1_Click(object sender, EventArgs e)
{
try
{
string imagePath = textBoxImagePath.Text;
string modelPath = textBoxModelPath.Text;
if (string.IsNullOrEmpty(imagePath) || string.IsNullOrEmpty(modelPath))
{
textBox1.Text = "請(qǐng)選擇圖像和模型路徑。";
return;
}
textBox1.Text = "開始運(yùn)行 ResNet ...";
// 設(shè)置圖像路徑和模型路徑
NativeMethods.set_image_path(imagePath);
NativeMethods.set_model_path(modelPath);
// 調(diào)用 DLL 執(zhí)行推理
IntPtr resultPtr = NativeMethods.run_resnet();
// 將返回的指針轉(zhuǎn)換為字符串
string result = Marshal.PtrToStringAnsi(resultPtr);
// 顯示結(jié)果
textBox1.Text = result;
}
catch (Exception ex)
{
textBox1.Text = "錯(cuò)誤: " + ex.Message;
}
}
}
}Program.cs
我們還需要一個(gè)入口主程序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ResNetApp
{
static class Program
{
/// <summary>
/// 應(yīng)用程序的主入口點(diǎn)。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
完成之后點(diǎn)擊生成 就可以在bin中出現(xiàn)的你的.exe文件咯,是不是很簡(jiǎn)單呀~[狗頭]
總結(jié)
到此這篇關(guān)于用c#制作一個(gè)小型桌面程序的文章就介紹到這了,更多相關(guān)c#制作小型桌面程序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)Socket服務(wù)器及多客戶端連接的方式
這篇文章介紹了C#實(shí)現(xiàn)Socket服務(wù)器及多客戶端連接的方式,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01
unity實(shí)現(xiàn)場(chǎng)景跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)場(chǎng)景跳轉(zhuǎn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
C#實(shí)現(xiàn)按照指定長(zhǎng)度在數(shù)字前補(bǔ)0方法小結(jié)
這篇文章主要介紹了C#實(shí)現(xiàn)按照指定長(zhǎng)度在數(shù)字前補(bǔ)0方法,實(shí)例總結(jié)了兩個(gè)常用的數(shù)字補(bǔ)0的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
C#使用分部類設(shè)計(jì)實(shí)現(xiàn)一個(gè)計(jì)算器
分部類是C#4.5中的一個(gè)新特性,它的出現(xiàn)使得程序的結(jié)構(gòu)更加合理,代碼組織更加緊密,本文將使用分部類設(shè)計(jì)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的計(jì)算器,感興趣的小伙伴可以了解下2024-02-02

