65.9K
CodeProject 正在变化。 阅读更多。
Home

我的照片编辑器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (15投票s)

2012 年 7 月 25 日

CPOL

4分钟阅读

viewsIcon

94334

downloadIcon

42004

这是关于如何使用 Visual Studio C++ 和 OpenCV 制作类似 Photoshop 的简单软件。

引言

在本文中,我将向您展示如何制作一个类似Photoshop的软件,但更简单、更小、更易于使用。您可以加载图像,将其转换为多种色彩空间(RGB、HSV、YCrCb),调整亮度/对比度,进行一些滤镜(噪点、模糊等),均衡直方图,查看图像信息等等。处理后的图像可以保存,并可选择质量。

这个小型软件是使用Microsoft Visual Studio 2010和OpenCV库(版本2.4.1)编写的。要使用代码,您必须具备一些基本的图像处理知识,了解如何在Visual Studio中使用MFC以及OpenCV。

使用代码 

我将这个程序分为三个部分:图像处理核心类、图像预览类和主处理。  

图像处理核心类包含许多处理函数,如调整亮度、添加噪点、边缘检测、图像旋转等。 所有这些函数都具有以下形式:  

// Image processing function in header file
void Brightness(cv::Mat &src, cv::Mat &dst, int val)

其中src是源图像,dst是处理后的图像,val是特定函数的数值。如果我们命名头文件为ImgProcessing,那么在实现文件中,上述函数如下所示: 

// ImgProcessing.cpp  file
void ImgProcessing::Brightness(Mat &src, Mat &dst, int val)
{
	if(src.channels() == 1)
	{
		for(int i = 0; i < src.rows; i++)
			for(int j = 0; j < src.cols; j++)
				dst.at<uchar>(i,j) = saturate_cast<uchar>(src.at<uchar>(i,j) + val);  
	}
 
	if(src.channels() == 3)
	{
		for(int i = 0; i < src.rows; i++)
			for(int j = 0; j < src.cols; j++)
				for(int k = 0; k < 3; k++)
					dst.at<Vec3b>(i,j)[k] = saturate_cast<uchar>(src.at<Vec3b>(i,j)[k] + val);
	}
}

图像预览类继承自CDialog类。该类具有统一的函数,允许我们更改参数并预览图像处理后的效果。

     

上图显示了预览(噪点和失真图像)的两种情况。事实上,在做出任何决定之前,有许多情况需要预览。由于每个函数都有其自身的类型和参数,因此预览对话框的数量将近等于处理函数的数量!这意味着我们必须创建大量的对话框并花费更多时间编写代码。这不是一个好的选择。为了避免创建许多预览对话框和编写更多代码,我们只创建一个并为其提供一些选项。预览对话框的外观和返回值将取决于主进程如何指示它。

例如,假设我们正在处理图像的亮度,当点击亮度调整菜单时,图像预览对话框将出现。OnInitDialog函数将定义如下:

BOOL ImgPreview::OnInitDialog()
{
	
	// Init components
	this->slider1.SetRange(0, 450);
	this->slider1.SetPos(225);
	this->slider2.SetRange(0, 450);
	this->slider2.SetPos(225);
	this->slider3.SetRange(0, 450);
	this->slider3.SetPos(225);
 
	this->slider2.EnableWindow(false);
	this->slider3.EnableWindow(false);
	
	
	switch(type)
	{
		case m_brightness:
		{
			this->param1.SetWindowTextA("Brightness");
		}
		break;
 
		.....
        }
	return true;
}

当我们更改图像预览对话框上的滑块位置时,图像的亮度必须更改并在图片框中显示。在MFC中有显示图像在控件上的方法(例如,picture control)。在本文中,我使用MFC的一个特殊unity函数来强制显示图像。 因此,让我们创建一个Picture Control或您喜欢的任何任意控件。将控件的ID更改为IDC_STATIC_IMG_PREVIEW,并在对话框初始化时,将以下代码放入OnInitDialog函数中:

BOOL ImgPreview::OnInitDialog()
{
	
	// Init components
	//....
	cv::namedWindow("Image Preview", 1);
	HWND hWnd = (HWND) cvGetWindowHandle("Image Preview");
	HWND hParent = ::GetParent(hWnd);
	::SetParent(hWnd, GetDlgItem(IDC_STATIC_IMG_PREVIEW)->m_hWnd);
	::ShowWindow(hParent, SW_HIDE);
	//....
	return true;
}

到目前为止,每次调用cv::imshow("Image Preview", image)函数时,它都会强制picture control显示图像。现在,更改滑块位置时的代码如下:  

void ImgPreview::OnNMCustomdrawSlider1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
	// TODO: Add your control notification handler code here
	cv::Mat img_dst = img_display.clone();
	switch(type)
	{
	case m_brightness:
	{
		i_param1 = slider1.GetPos() - (int)slider1.GetRangeMax()/2;
		l_disp1.SetWindowTextA(ToString(i_param1));
		process.Brightness(img_display, img_dst, i_param1);
		cv::imshow("Image Preview", img_dst);
		
	}
        // Other cases	
	//...
}}

img_display是一个图像,它是从源图像缩小的,以便在预览对话框中显示。

如果按下OK按钮,ImgPreview类中的is_ok成员将被设置为true,以便主进程知道一切似乎都正常。否则,它将被设置为false(这是两个对话框通信的一种方式)。

主进程基于MFC对话框,具有菜单系统、按钮和其他控件。让我们创建一个如下的菜单系统: 

 

然后为每个菜单添加事件处理程序。反转事件处理程序 看起来像这样:

void CMyPhotoEditorDlg::OnUpdateAdjustmentsInvert(CCmdUI *pCmdUI)
{
	// Invert Image
	process.Invert(src, src);
	ImageDisplay(src);
	.....
}  

其中processImgProcessing类的一个实例,而ImageDisplay()是一个强制在MFC控件中显示图像的函数。

如果需要预览处理图像的菜单,菜单事件处理程序将如下所示:

void CMyPhotoEditorDlg::OnUpdateAdjustmentsBrightness(CCmdUI *pCmdUI)
{
	// Adjust brightness
	ImgPreview dlg;
	dlg.SetType(m_brightness);
	dlg.src = src;
	dlg.DoModal();
	if(dlg.is_ok)
	{
		process.Brightness(src, src, dlg.i_param1);
		ImageDisplay(src);
		
		.....
	}
}

另一个重要的任务是验证菜单系统。否则,可能会发生一些错误。例如,如果我们尝试将灰度模式下的图像转换为hsv模式,或者如果我们尝试修改尚未加载(即空图像)的图像,程序可能会崩溃。验证菜单函数如下:

void CMyPhotoEditorDlg::ValidateMenu()
{
	// Validate menu
	CMenu *m_file = GetMenu();
	m_file->GetSubMenu(0);
 
	CMenu *m_image = GetMenu();
	m_image->GetSubMenu(1);
 
	....
 
	if(!is_load)
	{
		m_file->EnableMenuItem(ID_FILE_SAVE1, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_DISABLED|MF_GRAYED);
		....
	}
	
	else
	{
		m_file->EnableMenuItem(ID_FILE_SAVE1, MF_ENABLED);
		m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_ENABLED);
		....
	}
	if(is_gray)
	{
		m_image->EnableMenuItem(ID_MODE_HSV, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_YCrCb, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_RGBCOLOR, MF_DISABLED|MF_GRAYED);
	}
	if(is_hsv)
	{
		m_image->EnableMenuItem(ID_MODE_HSV, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_DISABLED|MF_GRAYED);
		m_image->EnableMenuItem(ID_MODE_YCrCb, MF_DISABLED|MF_GRAYED);
	}
	
        ....
}

视图菜单包含图像信息、直方图和历史记录。如果选择了这些菜单,它们将被勾选并在每一步之后更新。

void CMyPhotoEditorDlg::OnUpdateAdjustmentsBrightness(CCmdUI *pCmdUI)
{
	// Brightness
	ImgPreview dlg;
	...
	dlg.DoModal();
	if(dlg.is_ok)
	{
		....
		if(is_histogram) UpdateHistogram(0);
		if(is_history) history_list.AddString("Adjust Image Brightness");
	}
}

请注意,上述解释很简单,只提供了一些主要思路,许多带有注释的代码都很容易理解和使用。

历史   

版本 1.0.   

这是最简单的程序。我将带着更新的版本回来。

© . All rights reserved.