参考
1.「寒韩Glory」VS2015图形界面YOLO3应用程序
2.[扶摇直上九万里wyh]MFC OpenCV:显示图片的3种方法(详细)
主要参考1中的yolo相关配置属性,和2中的第3.2部分:转换格式显示OpenCV图片。
界面设计
设计的界面如下:
包含了导入图片、进行识别两个有效个按钮(界面中的加载网络按钮无效),两个picture control,一个识别耗时文本和一个可更改文本用于显示识别一幅图的时间。
2 ***Dlg.cpp文件
1.头文件部分
#define OPENCV
#define GPU
#include <mutex>
#include "yolo_v2_class.hpp"
2.网络初始化
本来是想将如下的网络的初始化部分放在加载网络按钮对应的操作函数中,但是由于Detector detector();不知道如何初始化为全局变量,因此直接将下列语句放在在函数外面,也就是全局变量创建时直接初始化,这样的话一打开MFC自动初始化。
//初始化网络
std::string names_file = "net/voc.names";
std::string cfg_file = "net/yolo.cfg";
std::string weights_file = "net/yolo.weights";
auto obj_names = objects_names_from_file(names_file);
Detector detector(cfg_file, weights_file);
除了上面的部分外,还在.cpp文件中添加了参考博客2中的opencv图像格式转MFC图像格式的函数MatToCImage:
void MatToCImage(cv::Mat &mat, CImage &cImage)
{OpenCV 图像转为 MFC 图像
//create new CImage
int width = mat.cols;
int height = mat.rows;
int channels = mat.channels();
cImage.Destroy(); //clear
cImage.Create(width, height, 8 * channels); //默认图像像素单通道占用1个字节
//copy values
uchar* ps;
uchar* pimg = (uchar*)cImage.GetBits(); //A pointer to the bitmap buffer
int step = cImage.GetPitch();
for (int i = 0; i < height; ++i)
{
ps = (mat.ptr<uchar>(i));
for (int j = 0; j < width; ++j)
{
if (channels == 1) //gray
{
*(pimg + i * step + j) = ps[j];
}
else if (channels == 3) //color
{
for (int k = 0; k < 3; ++k)
{
*(pimg + i * step + j * 3 + k) = ps[j * 3 + k];
}
}
}
}
}
3.读取网络配置文件和结果显示函数
这部分参考博客1,在darknet中有个yolo_console_dll示例程序,其中涵盖了对视频、图像等的应用,仿照这个程序就可以编制自己的各类应用了。
//读取网络配置文件之类别名称
std::vector<std::string> objects_names_from_file(std::string const filename) {
std::ifstream file(filename);
std::vector<std::string> file_lines;
if (!file.is_open()) return file_lines;
for (std::string line; getline(file, line);) file_lines.push_back(line);
std::cout << "object names loaded \n";
return file_lines;
}
// 给图像中画分类框
void draw_boxes(cv::Mat mat_img, std::vector<bbox_t> result_vec, std::vector<std::string> obj_names,
int current_det_fps = -1, int current_cap_fps = -1)
{
int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };
for (auto &i : result_vec) {
cv::Scalar color = obj_id_to_color(i.obj_id);
cv::rectangle(mat_img, cv::Rect(i.x, i.y, i.w, i.h), color, 2);
if (obj_names.size() > i.obj_id) {
std::string obj_name = obj_names[i.obj_id];
if (i.track_id > 0) obj_name += " - " + std::to_string(i.track_id);
cv::Size const text_size = getTextSize(obj_name, cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, 2, 0);
int const max_width = (text_size.width > i.w + 2) ? text_size.width : (i.w + 2);
cv::rectangle(mat_img, cv::Point2f(std::max((int)i.x - 1, 0), std::max((int)i.y - 30, 0)),
cv::Point2f(std::min((int)i.x + max_width, mat_img.cols - 1), std::min((int)i.y, mat_img.rows - 1)),
color, CV_FILLED, 8, 0);
putText(mat_img, obj_name, cv::Point2f(i.x, i.y - 10), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(0, 0, 0), 2);
}
}
if (current_det_fps >= 0 && current_cap_fps >= 0) {
std::string fps_str = "FPS detection: " + std::to_string(current_det_fps) + " FPS capture: " + std::to_string(current_cap_fps);
putText(mat_img, fps_str, cv::Point2f(10, 20), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(50, 255, 0), 2);
}
}
4.导入与识别图像按钮动作函数
这两个按钮的函数放在上面几个函数和语句的后面,否则有可能报错,未找到函数或变量。
void CMFCyolo3Dlg::OnBnClickedLoad()
{
// TODO: 在此添加控件通知处理程序代码
//导入图片
CFileDialog dlg(TRUE, _T("*.jpg;*.jpeg"), 0, 6UL, _T("Image(*.jpg;*.jpeg)|*.jpg;*.jpeg||"));
if (dlg.DoModal() != IDOK)return;
std::string filename = CT2A(dlg.GetPathName());
mat_img = cv::imread(filename);
//转换图片格式并显示
UpdateWindow();//刷新窗口
CRect rect;//定义矩形类
CWnd *pWnd = GetDlgItem(IDC_STATIC);//获取控件句柄
pWnd->GetClientRect(&rect); //获取句柄指向控件区域的大小
CDC *pDc = pWnd->GetDC();//获取picture的DC
int win_w = rect.Width(), win_h = rect.Height();//获取窗口宽高
int img_w = img_small.cols, img_h = img_small.rows;//获取图片宽高
//char str1[50] = "predictions";
//缩放图片
cv::Size dsize = cv::Size(win_w, win_h);
img_small = cv::Mat(dsize, CV_32S);
resize(mat_img, img_small, dsize);
//绘图
CImage ImageCam_test;
MatToCImage(img_small, ImageCam_test);//转换图片格式
pDc->SetStretchBltMode(COLORONCOLOR);
ImageCam_test.Draw(pDc->m_hDC, 0, 0, win_w, win_h, 0, 0, win_w, win_h);//画出图片
ReleaseDC(pDc);
}
void CMFCyolo3Dlg::OnBnClickedCalculate()
{
// TODO: 在此添加控件通知处理程序代码
auto start = std::chrono::steady_clock::now();
float thr = 0.2;
std::vector<bbox_t> result_vec = detector.detect(img_small,thr);
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> spent = end - start;
CString msg;
msg.Format(_T("%lf sec"), spent.count());
SetDlgItemText(IDC_EDIT1, msg);
draw_boxes(img_small, result_vec, obj_names);
//转换图片格式并显示
UpdateWindow();//刷新窗口
CRect rect_test;//定义矩形类
CWnd *pWnd_test = GetDlgItem(IDC_STATIC2);//获取控件句柄
pWnd_test->GetClientRect(&rect_test); //获取句柄指向控件区域的大小
CDC *pDc_test = pWnd_test->GetDC();//获取picture的DC
int win_w = rect_test.Width(), win_h = rect_test.Height();//获取窗口宽高
int img_w = img_small.cols, img_h = img_small.rows;//获取图片宽高
//缩放图片
cv::Size dsize = cv::Size(win_w, win_h);
cv::Mat img_small_test = cv::Mat(dsize, CV_32S);;
resize(img_small, img_small_test, dsize);
//绘图
CImage ImageCam_test;
MatToCImage(img_small_test, ImageCam_test);//转换图片格式
pDc_test->SetStretchBltMode(COLORONCOLOR);
ImageCam_test.Draw(pDc_test->m_hDC, 0, 0, win_w, win_h, 0, 0, win_w, win_h);//画出图片
ReleaseDC(pDc_test);
}
三、**Dlg.h文件
主要将opencv的头文件和类中添加成员函数
#include "opencv2/opencv.hpp"
//using namespace cv;
Mat mat_img,img_small;
有待改进的地方
图像的resize部分是否可以省略一次
添加识别结果的置信度显示功能(需要改动源码并重新编译);
添加存图功能,并将识别结果图片按序列重命名;
改进模型的识别精度
附录
完成的***Dlg.cpp文件
// MFCyolo3Dlg.cpp : 实现文件
#include "stdafx.h"
#include "MFCyolo3.h"
#include "MFCyolo3Dlg.h"
#include "afxdialogex.h"
#define OPENCV
#define GPU
#include <mutex>
#include "yolo_v2_class.hpp"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CMFCyolo3Dlg 对话框
CMFCyolo3Dlg::CMFCyolo3Dlg(CWnd* pParent /*=NULL*/)
: CDialogEx(IDD_MFCYOLO3_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCyolo3Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMFCyolo3Dlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_LOAD, &CMFCyolo3Dlg::OnBnClickedLoad)
ON_BN_CLICKED(IDC_CALCULATE, &CMFCyolo3Dlg::OnBnClickedCalculate)
//ON_EN_CHANGE(IDC_EDIT1, &CMFCyolo3Dlg::OnEnChangeEdit1)
ON_BN_CLICKED(IDC_INIT, &CMFCyolo3Dlg::OnBnClickedInit)
END_MESSAGE_MAP()
// CMFCyolo3Dlg 消息处理程序
BOOL CMFCyolo3Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CMFCyolo3Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CMFCyolo3Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCyolo3Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void MatToCImage(cv::Mat &mat, CImage &cImage)
{OpenCV 图像转为 MFC 图像
//create new CImage
int width = mat.cols;
int height = mat.rows;
int channels = mat.channels();
cImage.Destroy(); //clear
cImage.Create(width, height, 8 * channels); //默认图像像素单通道占用1个字节
//copy values
uchar* ps;
uchar* pimg = (uchar*)cImage.GetBits(); //A pointer to the bitmap buffer
int step = cImage.GetPitch();
for (int i = 0; i < height; ++i)
{
ps = (mat.ptr<uchar>(i));
for (int j = 0; j < width; ++j)
{
if (channels == 1) //gray
{
*(pimg + i * step + j) = ps[j];
}
else if (channels == 3) //color
{
for (int k = 0; k < 3; ++k)
{
*(pimg + i * step + j * 3 + k) = ps[j * 3 + k];
}
}
}
}
}
//读取网络配置文件之类别名称
std::vector<std::string> objects_names_from_file(std::string const filename) {
std::ifstream file(filename);
std::vector<std::string> file_lines;
if (!file.is_open()) return file_lines;
for (std::string line; getline(file, line);) file_lines.push_back(line);
std::cout << "object names loaded \n";
return file_lines;
}
// 给图像中画分类框
void draw_boxes(cv::Mat mat_img, std::vector<bbox_t> result_vec, std::vector<std::string> obj_names,
int current_det_fps = -1, int current_cap_fps = -1)
{
int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };
for (auto &i : result_vec) {
cv::Scalar color = obj_id_to_color(i.obj_id);
cv::rectangle(mat_img, cv::Rect(i.x, i.y, i.w, i.h), color, 2);
if (obj_names.size() > i.obj_id) {
std::string obj_name = obj_names[i.obj_id];
if (i.track_id > 0) obj_name += " - " + std::to_string(i.track_id);
cv::Size const text_size = getTextSize(obj_name, cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, 2, 0);
int const max_width = (text_size.width > i.w + 2) ? text_size.width : (i.w + 2);
cv::rectangle(mat_img, cv::Point2f(std::max((int)i.x - 1, 0), std::max((int)i.y - 30, 0)),
cv::Point2f(std::min((int)i.x + max_width, mat_img.cols - 1), std::min((int)i.y, mat_img.rows - 1)),
color, CV_FILLED, 8, 0);
putText(mat_img, obj_name, cv::Point2f(i.x, i.y - 10), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(0, 0, 0), 2);
}
}
if (current_det_fps >= 0 && current_cap_fps >= 0) {
std::string fps_str = "FPS detection: " + std::to_string(current_det_fps) + " FPS capture: " + std::to_string(current_cap_fps);
putText(mat_img, fps_str, cv::Point2f(10, 20), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(50, 255, 0), 2);
}
}
//初始化网络
std::string names_file = "net/voc.names";
std::string cfg_file = "net/yolo.cfg";
std::string weights_file = "net/yolo.weights";
auto obj_names = objects_names_from_file(names_file);
Detector detector(cfg_file, weights_file);
void CMFCyolo3Dlg::OnBnClickedLoad()
{
// TODO: 在此添加控件通知处理程序代码
//导入图片
CFileDialog dlg(TRUE, _T("*.jpg;*.jpeg"), 0, 6UL, _T("Image(*.jpg;*.jpeg)|*.jpg;*.jpeg||"));
if (dlg.DoModal() != IDOK)return;
std::string filename = CT2A(dlg.GetPathName());
mat_img = cv::imread(filename);
//转换图片格式并显示
UpdateWindow();//刷新窗口
CRect rect;//定义矩形类
CWnd *pWnd = GetDlgItem(IDC_STATIC);//获取控件句柄
pWnd->GetClientRect(&rect); //获取句柄指向控件区域的大小
CDC *pDc = pWnd->GetDC();//获取picture的DC
int win_w = rect.Width(), win_h = rect.Height();//获取窗口宽高
int img_w = img_small.cols, img_h = img_small.rows;//获取图片宽高
//缩放图片
cv::Size dsize = cv::Size(win_w, win_h);
img_small = cv::Mat(dsize, CV_32S);
resize(mat_img, img_small, dsize);
//绘图
CImage ImageCam_test;
MatToCImage(img_small, ImageCam_test);//转换图片格式
pDc->SetStretchBltMode(COLORONCOLOR);
ImageCam_test.Draw(pDc->m_hDC, 0, 0, win_w, win_h, 0, 0, win_w, win_h);//画出图片
ReleaseDC(pDc);
}
void CMFCyolo3Dlg::OnBnClickedCalculate()
{
// TODO: 在此添加控件通知处理程序代码
auto start = std::chrono::steady_clock::now();
float thr = 0.2;
std::vector<bbox_t> result_vec = detector.detect(img_small,thr);
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> spent = end - start;
CString msg;
msg.Format(_T("%lf sec"), spent.count());
SetDlgItemText(IDC_EDIT1, msg);
draw_boxes(img_small, result_vec, obj_names);
//转换图片格式并显示
UpdateWindow();//刷新窗口
CRect rect_test;//定义矩形类
CWnd *pWnd_test = GetDlgItem(IDC_STATIC2);//获取控件句柄
pWnd_test->GetClientRect(&rect_test); //获取句柄指向控件区域的大小
CDC *pDc_test = pWnd_test->GetDC();//获取picture的DC
int win_w = rect_test.Width(), win_h = rect_test.Height();//获取窗口宽高
int img_w = img_small.cols, img_h = img_small.rows;//获取图片宽高
//缩放图片
cv::Size dsize = cv::Size(win_w, win_h);
cv::Mat img_small_test = cv::Mat(dsize, CV_32S);;
resize(img_small, img_small_test, dsize);
//绘图
CImage ImageCam_test;
MatToCImage(img_small_test, ImageCam_test);//转换图片格式
pDc_test->SetStretchBltMode(COLORONCOLOR);
ImageCam_test.Draw(pDc_test->m_hDC, 0, 0, win_w, win_h, 0, 0, win_w, win_h);//画出图片
ReleaseDC(pDc_test);
}
void CMFCyolo3Dlg::OnBnClickedInit()
{
}