我的照片编辑器






4.83/5 (15投票s)
这是关于如何使用 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);
.....
}
其中process
是ImgProcessing
类的一个实例,而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.
这是最简单的程序。我将带着更新的版本回来。