小言_互联网的博客

openCV4.2+MFC制作多页图片浏览器(无CvvImage)

494人阅读  评论(0)

学院一门有关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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场