学院一门有关openCV的自发课,第一次作业是用VC和openCV的接口做一个可浏览图片的图片浏览器。
大二的一门自发课学的MFC,说实话是很老的框架了,但是为了对付作业还是直接用好了……最后写代码的时候发现openCV部分只有几行,MFC各种奇诡的问题倒是占用了绝大部分时间。
openCV用的是最新版本的4.2,网上普遍相关贴子都是3.4甚至2.2版本……广为流传的使用旧版本“CvvImage”类的方法没办法作用,很多方法名都换掉了,非常痛苦,只好想别的办法。(两个时间跨度这么大的框架一起用也只有各种课程实验了吧……)
唉,花了这么多时间就应付了一个作业,总感觉好亏啊!虽然估计以后99%不会用MFC甚至不会写C了,但写个博客记录一下,稍微有点价值罢!
预览
一页固定8个图片栏位,点击导入图片会用openCV的接口遍历指定文件夹下的图片文件。
点击按钮翻页
点击后会显示原图,点击图片两侧也可以翻阅前后的图片。
主要实现
布局就8个Picture Control。
在Dlg里定义一个静态成员mats存储所有opencv读取的mat数据。
public:
static vector<cv::Mat> mats;
因为MFC的点击事件不能传参(至少我不知道咋传),只能一个控件绑定一个对应事件,所以8个picture control控件对应8个函数?但这也太蠢了……
思考了很久也不知道有什么优雅的书写方式,就想到了一个不那么蠢的方法,8个picture control的点击事件绑定同一个方法,该方法再通过获取鼠标的坐标判断点击的是哪个控件。
好吧,看上去还是很蠢……
// 图片控件位置列表
vector<CRect> rects;
// 一页的图片数量
int image_count = 8;
// 当前页数
int page = 1;
cv::String folder = "E:\\study-计算机图像\\images";
int image_ids[8] = { IDC_IMG1, IDC_IMG2, IDC_IMG3, IDC_IMG4,
IDC_IMG5, IDC_IMG6, IDC_IMG7, IDC_IMG8 };
8个控件绑定同一个方法……(; ´_ゝ`)
BEGIN_MESSAGE_MAP(CImageViewerDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON1, &CImageViewerDlg::OnBnClickedButton1)
ON_STN_CLICKED(IDC_IMG1, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG2, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG3, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG4, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG5, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG6, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG7, &CImageViewerDlg::OnStnClickedImg)
ON_STN_CLICKED(IDC_IMG8, &CImageViewerDlg::OnStnClickedImg)
ON_BN_CLICKED(IDC_BUTTON2, &CImageViewerDlg::OnBnClickedButton2)
ON_BN_CLICKED(IDC_BUTTON3, &CImageViewerDlg::OnBnClickedButton3)
END_MESSAGE_MAP()
在写对应的方法前,在OnInitDialog()里初始化8个控件的位置。这里转换成了相对坐标那后面的判定也要用相对坐标。
// 初始化rects列表
for (int i = 0; i < 8; i++)
{
CRect rect;
GetDlgItem(image_ids[i])->GetWindowRect(&rect);
ScreenToClient(&rect); //转到client
rects.push_back(rect);
}
点击“导入图片”会初始化mats列表,调用cv::glob()和imread()读入Mat文件,然后显示第一页。
void CImageViewerDlg::OnBnClickedButton1()
{
//Invalidate(TRUE);
std::vector<cv::String> filenames;
cv::glob(folder, filenames);
mats.clear();
for (int i = 0; i < filenames.size(); i++)
{
cv::Mat imgSrc = cv::imread(filenames[i]);
// 存储原图
mats.push_back(imgSrc);
}
this->page = 1;
JumpToPage(1);
}
“前一页”和“后一页”的点击事件,主要是判断页数合不合法以及更改按钮的可用状态,然后调用showPreview()方法显示图像。
void CImageViewerDlg::OnBnClickedButton2()
{
// 上一页
JumpToPage(page - 1);
}
void CImageViewerDlg::OnBnClickedButton3()
{
// 下一页
JumpToPage(page + 1);
}
void CImageViewerDlg::JumpToPage(int page)
{
if (page <= 0 || (page - 1)*image_count >= mats.size())
{
MessageBox("页数不合法!");
return;
}
this->page = page;
GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON3)->EnableWindow(TRUE);
if (page == 1)
{
GetDlgItem(IDC_BUTTON2)->EnableWindow(FALSE);
}
if (page*image_count >= mats.size())
{
GetDlgItem(IDC_BUTTON3)->EnableWindow(FALSE);
}
int pages = mats.size() / image_count == 0 ? mats.size() / image_count : mats.size() / image_count + 1;
CString str;
str.Format("当前%d/%d页,共%d张图片",page,pages,mats.size());
GetDlgItem(IDC_TEXT)->SetWindowTextA(_T(str));
showPreview();
}
显示图像的部分GetDlgItem(image_ids[i])->ShowWindow(FALSE);
GetDlgItem(image_ids[i])->ShowWindow(TRUE);
这两句用来刷新picture control的内容。
对mat图像大小进行重置,随后用调用Mat2CImage()方法将Mat数据转换成CImage写入控件。
void CImageViewerDlg::showPreview()
{
for (int i = 0; i < image_count; i++)
{
GetDlgItem(image_ids[i])->ShowWindow(FALSE);
GetDlgItem(image_ids[i])->ShowWindow(TRUE);
}
int start = (page - 1)*image_count;
int end = min(page*image_count - 1, (int)mats.size() - 1);
for (int i = 0, j = start; j <= end; i++,j++)
{
cv::Mat imgSrc = mats[j];
//缩略图
CRect rect;
//rect = rects[i];
GetDlgItem(image_ids[i])->GetClientRect(&rect);
cv::Size winSize(rect.right, rect.bottom);
// Resize the source to the size of the destination image if necessary
cv::Mat cvImgTmp(winSize, CV_8UC3);
if (imgSrc.size() != winSize)
{
cv::resize(imgSrc, cvImgTmp, winSize);
}
else
{
cvImgTmp = imgSrc;
}
CImage imgDst;
Mat2CImage(cvImgTmp, imgDst);
imgDst.Draw(GetDlgItem(image_ids[i])->GetDC()->GetSafeHdc(), rect);
}
}
Mat2CImage()函数。
void Mat2CImage(cv::Mat &mat, CImage &cImage)
{
//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];
}
}
}
}
}
这样就能在主对话框上显示8幅图片了,然后来制作点击图片显示新的原图对话框的效果。
首先布局一下弹出的对话框,预览界面的对话框。大概摆一下就行,因为我想Dialog大小随图片大小改变。
public:
ImageDetailDlg(int index,CWnd* pParent = nullptr); // 标准构造函数
virtual ~ImageDetailDlg();
cv::Mat mat_show;
int index;
构造函数传入图片在mats数组内的序号。
ImageDetailDlg::ImageDetailDlg(int index, CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_DIALOG1, pParent)
{
this->index = index;
mat_show = CImageViewerDlg::mats[index];
}
四个方法。
void changeSize();
void changeImage(int);
void changeBtn();
void showImage(cv::Mat mat);
OnInitDialog()方法内调用changeSize(),该方法用于让控件大小随图片大小变化,以及调整按钮和文本的位置。因为OnInitDialog()完成后会调用OnPaint(),所以绘图的过程(显示图片)要写在OnPaint()中。
BOOL ImageDetailDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
changeSize();
return TRUE;
}
changeSize()方法,主要是调用控件的MoveWindow()
方法,让IDC_SHOW即picture control的大小和该窗体大小跟图片一致。不过窗体大小跟客户区大小有一点边缘上的差别,其实并不相等,我这里就忽略了。
void ImageDetailDlg::changeSize()
{
cv::Size size = mat_show.size();
CRect pos;
GetWindowRect(&pos);
//适应屏幕大小
MoveWindow(pos.left, pos.top, size.width, size.height + 100);
GetWindowRect(&pos);
CRect rect;
//更改按钮位置
GetDlgItem(IDC_LEFT)->GetClientRect(&rect);//按钮大小
int width = rect.Width();
GetDlgItem(IDC_LEFT)->MoveWindow(pos.Width() / 2 - 40 - width, pos.Height() - rect.Height() - 50, rect.Width(), rect.Height());
GetDlgItem(IDC_RIGHT)->MoveWindow(pos.Width() / 2 + 40, pos.Height() - rect.Height() - 50, rect.Width(), rect.Height());
GetDlgItem(IDC_RIGHT)->GetClientRect(&rect);
CString s;
s.Format("%d/%d", index + 1, CImageViewerDlg::mats.size());
GetDlgItem(IDC_INDEX)->SetWindowTextA(_T(s));
GetDlgItem(IDC_INDEX)->GetClientRect(&rect);//文本大小
GetDlgItem(IDC_INDEX)->MoveWindow(pos.Width() / 2 - 10 , pos.Height() - rect.Height() - 55, 50, rect.Height());
GetDlgItem(IDC_SHOW)->MoveWindow(0, 0, size.width, size.height);
GetDlgItem(IDC_SHOW)->GetClientRect(&rect);
}
OnInitDialog()结束后会调用OnPaint(),在OnPaint()中进行图像的显示。
void ImageDetailDlg::OnPaint()
{
// 显示传来的mat并改变
showImage(mat_show);
CDialogEx::OnPaint();
}
跟主页面的代码差不多。
void ImageDetailDlg::showImage(cv::Mat mat)
{
CImage imgDst;
Mat2CImage(mat, imgDst);
CRect rect;
GetDlgItem(IDC_SHOW)->GetClientRect(&rect);
imgDst.Draw(GetDlgItem(IDC_SHOW)->GetDC()->GetSafeHdc(), rect);
}
接着实现点击翻页的效果,一种是点击按钮,一种是点击图片的左右两侧,实现都差不多。
这样图片的预览界面就完成了,最后再在主页面写完8个控件对应的点击事件方法就ok了。
void ImageDetailDlg::changeImage(int index)
{
if (index < 0 || index >= CImageViewerDlg::mats.size())
{
return;
}
mat_show = CImageViewerDlg::mats[index];
this->index = index;
GetDlgItem(IDC_SHOW)->ShowWindow(FALSE);
GetDlgItem(IDC_SHOW)->ShowWindow(TRUE);
changeSize();
showImage(mat_show);
changeBtn();
}
void ImageDetailDlg::changeBtn()
{
GetDlgItem(IDC_LEFT)->EnableWindow(TRUE);
GetDlgItem(IDC_RIGHT)->EnableWindow(TRUE);
if (index == 0)
{
GetDlgItem(IDC_LEFT)->EnableWindow(FALSE);
}
if (index == CImageViewerDlg::mats.size() - 1)
{
GetDlgItem(IDC_RIGHT)->EnableWindow(FALSE);
}
}
void ImageDetailDlg::OnBnClickedLeft()
{
changeImage(index - 1);
}
void ImageDetailDlg::OnBnClickedRight()
{
changeImage(index + 1);
}
void ImageDetailDlg::OnStnClickedShow()
{
CRect img_rect;
// 点击图片可翻页
GetDlgItem(IDC_SHOW)->GetWindowRect(&img_rect);
ScreenToClient(img_rect);
CPoint point;
GetCursorPos(&point);
ScreenToClient(&point);
if (point.x > img_rect.Width() / 2)
{
changeImage(index + 1);
}
else
{
changeImage(index - 1);
}
}
对了,要给picture control添加点击事件,需要更改他的Notify属性。
点击控件后,获取鼠标坐标判断点击的是哪个控件,随后创建相应的预览对话框ImageDetailDlg。
void CImageViewerDlg::OnStnClickedImg()
{
//MessageBox("click");
// 判断点击的是哪个图片
RECT rect;
// 获取鼠标的相对坐标
CPoint point;
GetCursorPos(&point);
ScreenToClient(&point);
for (int i = 0; i < rects.size(); i++)
{
rect = rects[i];
if (point.x> rect.left && point.x < rect.right
&& point.y> rect.top && point.y <rect.bottom)
{
if ((page - 1)*image_count + i >= mats.size())
return;
int index = (page - 1)*image_count + i;
ImageDetailDlg* dlg = new ImageDetailDlg(index, this);
dlg->DoModal();
break;
}
}
}
转载:https://blog.csdn.net/qq_36544876/article/details/104707041