用OpenCV与MFC写一个简单易用的图像处理程序
工厂里做SOP及测试报告以及员工资格鉴定等常需用到简单的图像处理,PS等软件正版费用不菲,学习起来成本也高。Windows自带的图像处理软件,用起来也不是那么得心应手。因此我用OpenCV与MFC写了一个简单易用的图像处理程序。
程序界面
基于简单易用的目的,程序界面采用了对话框界面。如下:
基于做处理SOP及测试报告处理等应用场景,程序中设置了转灰度图、画直线、画圆 、画椭圆、画矩形、画指引线、放置文字等功能按键。如下:
做为图像处理软件,缩放、旋转、磨皮、对比度、亮度调节、图像锐化、抠图、裁剪、复制粘贴、局部修改、复制到剪贴板、Undo、Redo等功能也是必须的,本程序也实现了这些功能。 程序的最下面设置了类似Autocad的状态栏,左边显示程序状态信息及操作提示信,右边显示鼠标 指针所在位置的坐标及RGB值。
对话框程序有放大缩小功能, MFC要实现对话框放大缩小还比较麻烦需重写OnSize(UINT nType, int cx, int cy)函数,确定控件相对位置的变化. 其代码如下:
void CEasyImageDlg::OnSize(UINT nType, int cx, int cy)
{CDialogEx::OnSize(nType, cx, cy);CDialogEx::OnSize(nType, cx, cy);//先判断窗口是不是最小化了,因为窗口最小化之后 ,窗口的长和宽会变成0,当前一次变化的时就会出现除以0的错误操作if (nType != SIZE_MINIMIZED){ReSize();Invalidate();}}
//End of OnSize(UINT nType, int cx, int cy)
由于要处理的内容较多,单独写了一个ReSize()函数,其代码如下:
void CEasyImageDlg::ReSize()
{float fChangeRatio[2];int iNewWidth, iNewHeight;LOGFONT mLF;CFont font;memset(&mLF, 0, sizeof(LOGFONT));font.GetLogFont(&mLF);GetClientRect(&mRect); //取客户区大小iNewWidth = mRect.Width();iNewHeight = mRect.Height();fChangeRatio[0] = (float)iNewWidth / iOldWidth;fChangeRatio[1] = (float)iNewHeight / iOldHeight;mLF.lfHeight *= fChangeRatio[1];mLF.lfWidth *= fChangeRatio[0];_tcscpy_s(mLF.lfFaceName, LF_FACESIZE,_T("宋体"));//font.CreatePointFont(mLF.lfHeight, _T("宋体"));//font.CreatePointFontIndirect(&mLF);font.CreateFontIndirect(&mLF);iOldWidth = iNewWidth;iOldHeight = iNewHeight;int mCtrlID;CPoint oldTLPoint, tlPoint; //左上角 CPoint oldBRPoint, brPoint; //右下角 HWND hwndChild = ::GetWindow(m_hWnd, GW_CHILD); //列出所有控件while (hwndChild) {mCtrlID = ::GetDlgCtrlID(hwndChild);//取得ID GetDlgItem(mCtrlID)->GetWindowRect(mRect);ScreenToClient(mRect);oldTLPoint = mRect.TopLeft();tlPoint.x = long(oldTLPoint.x * fChangeRatio[0]);tlPoint.y = long(oldTLPoint.y * fChangeRatio[1]);oldBRPoint = mRect.BottomRight();brPoint.x = long(oldBRPoint.x * fChangeRatio[0]);brPoint.y = long(oldBRPoint.y * fChangeRatio[1]);mRect.SetRect(tlPoint, brPoint);GetDlgItem(mCtrlID)->MoveWindow(mRect, TRUE);GetDlgItem(mCtrlID)->SetFont(&font, 1);hwndChild = ::GetWindow(hwndChild, GW_HWNDNEXT);}}
程序功能实现编程
下面逐一讲解程序功能实现编程。
1 打开图像
打开图像需要完成以下几个动作:导入图像文件路径及名称,读入图像数据,显示打开图像,程序状态显示。导入图像文件路径及名称用MFC CFileDialog来实现。读入图像数据用OpenCV的imread函数实现,打开图像显示比较麻烦,OpenCV有一个显示图像程序imshow,但是OpenCV高版本的显示窗口并不能很好得嵌入MFC对话框客户区,此处的做法是,将Mat对象中的数据传递到CImage对象的内存中,用CImage的BitBlt函数来显示图像。状态显示通过MFC中 SetWindowTextW函数通过静态控件来显示. 打开图像的代码如下:
void CEasyImageDlg::OnBnClickedOpen()
{mString = "正在进行“打开图像操作”...";mInformation.SetWindowTextW(mString);CFileDialog fdlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("All files(*.*)|*.*||"));if (fdlg.DoModal() == IDOK){m_Path = fdlg.GetPathName();m_strEx = fdlg.GetFileExt();m_strName = fdlg.GetFileName();m_Path.ReleaseBuffer();m_strEx.ReleaseBuffer();m_strName.ReleaseBuffer();m_str = CT2A(m_Path);src = imread(m_str);if (src.empty()){mString.Format(L"打开图像文件%s失败,文件格式不正确或文件已损坏!", m_strName);mInformation.SetWindowTextW(mString);}else{dst[0] = dst[1] = src;matContainer.clear();MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate(); //fresh screen & display imagemString.Format(L"已打开:%s ", m_Path);mInformation.SetWindowTextW(mString); //diplay opened image information}}else{mString = "“打开图像”操作已取消!";mInformation.SetWindowTextW(mString);}
}
由于OpenCV支持多种图像格式, 因此可以打开OpenCV所支持的所有图像格式.
打开图像的效果如下:
打开后如下:
2 保存
保存图像分两步: 第一步,获取存储路径及文件名。第二步, 图像文件写入磁盘。获取存储路径与文件名可以用MFC CFileDialog来实现。图像文件的写入用OpenCV的imwrite函数来实现。保存的代码如下:
void CEasyImageDlg::OnBnClickedSave()
{mString = "正在进行“图像存储”操作...";mInformation.SetWindowTextW(mString);CString mfilter = _T("图片文件(*.bmp *.png *.jpg *.webp *.tif)|*.bmp;*.png;*jpg,*.webp,*.tif|All Files (*.*)|*.*||");CFileDialog fdlg(FALSE, NULL, 0, OFN_OVERWRITEPROMPT, mfilter, NULL);if (fdlg.DoModal() == IDOK){m_Path = fdlg.GetPathName();m_strEx = fdlg.GetFileExt();m_strName = fdlg.GetFileName();m_Path.ReleaseBuffer();m_strEx.ReleaseBuffer();m_strName.ReleaseBuffer();}else{mString = "“图像存储”操作已被取消";mInformation.SetWindowTextW(mString);return;}if (m_strEx == "BMP" || m_strEx == "bmp" || m_strEx == "dib" || m_strEx == "TIF" || m_strEx == "tif" || m_strEx == "tiff" || m_strEx == "PNG" || m_strEx == "png"|| m_strEx == "jpg" || m_strEx == "JPG" || m_strEx == "jpe" || m_strEx == "jpeg" || m_strEx == "jp2" || m_strEx == "webp" || m_strEx == "avif" || m_strEx == "pbm"|| m_strEx == "pgm" || m_strEx == "ppm" || m_strEx == "pxm" || m_strEx == "pnm" || m_strEx == "pfm" || m_strEx == "sr" || m_strEx == "ras" || m_strEx == "exr"|| m_strEx == "hdr" || m_strEx == "pic"){m_str = CT2A(m_Path);imwrite(m_str, dst[1]);mString.Format(L"图像文件已存储到:%s ", m_Path);mInformation.SetWindowTextW(mString);}else if (m_strEx == ""){m_Path += ".bmp";m_str = CT2A(m_Path);imwrite(m_str, dst[1]);mString.Format(L"图像文件已存储到:%s ", m_Path);mInformation.SetWindowTextW(mString);}else{mString = "输入文件格式不正确,图像未保存!";mInformation.SetWindowTextW(mString);}
}
运行效果如下:
保存后的效果如下:
状态栏中显示了存储的路径及文件名。可以存储为OpenCv所支持的任意格式,因此可以用改程序来实现图像文件格式的转换。
3 磨皮
磨皮操作的实质是模糊,减小对比度,使图像中·的·斑点显得不是那么明显。因此经磨皮处理的图片会变得不够清晰。这里的磨皮是用OpenCV的高斯模糊实现的。磨皮的源代码如下:
void CEasyImageDlg::OnBnClickedSmooth()
{mString = "正在进行“磨皮”操作...";mInformation.SetWindowTextW(mString);if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();}Mat ycrcb;cvtColor(dst[1], ycrcb, COLOR_BGR2YCrCb);vector<Mat> channels(3);split(ycrcb, channels);Mat yChannel = channels[0];Mat blurredY;GaussianBlur(yChannel, blurredY, cv::Size(15, 15), 0);channels[0] = blurredY;Mat smoothedYcrcb;merge(channels, smoothedYcrcb);cvtColor(smoothedYcrcb, dst[1], COLOR_YCrCb2BGR);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();mString = "“磨皮”操作已完成!";mInformation.SetWindowTextW(mString);
}
下面打开一张图片,试一下磨皮效果。磨皮前的图片:
3次磨皮后的图片,如下:
皮肤变得光滑些了,图片也变得不够清晰了,这样的图片处理最好是用局部磨皮,即本程序中的选择区域磨皮。
4 锐化
锐化可以使图片的交界处对比度更大,磨皮后的图片可以运用一到两次锐化。本处锐化处理是通过OpenCV图像卷积处理实现的。其代码如下:
void CEasyImageDlg::OnBnClickedSharp()
{if (dst[1].data){mString = "正在进行“图像锐化”操作...";mInformation.SetWindowTextW(mString);Invalidate();if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);filter2D(dst[1], dst[1], dst[1].depth(), kernel);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();mString = "“图像锐化”操作已完成";mInformation.SetWindowTextW(mString);}else{mString = "没发现打开的图像,“图像锐化”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}
}
下面是,磨皮后的图片经锐化后的效果:
5 转灰度图
转灰度图实现很容易,直接调用OpenCV的cvtColor函数即可,其代码如下:
void CEasyImageDlg::OnBnClickedCvtGray()
{if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();cvtColor(dst[1], dst[1], COLOR_BGR2GRAY);MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();mString = "“转灰度图”操作已完成。";mInformation.SetWindowTextW(mString);}else{mString = "没发现打开的图像,“转灰度图”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}
下面打开已张图,看下运行效果。转灰度图前:
转灰度图后:
6 画直线、画圆、画椭圆、画矩形、画指引线
为实现画图时拖动鼠标时的橡皮筋效果,这里需用到模式设置,即SetROP2函数。也就是说用MFC实现画图的橡皮筋效果,用OpenCV的line、circle、ellipse、rectangle、arrowedLine、putText等函数将直线、圆、椭圆、矩形、指引线画到图片上。这里以以画圆为例列出代码.画圆按钮事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedCircle()
{// TODO: 在此添加控件通知处理程序代码iFlag = 3; //draw circle flagmString = "开始“画圆”操作,请将鼠标指针移动到适当位置,按下鼠标左键不松,拖动鼠标,在适当位置松开鼠标左键,完成画圆。如需中途取消,请按Esc键。";mInformation.SetWindowTextW(mString);
}
鼠标左键按下事件处理函数,代码:
case 3://draw circlebFlag = true;::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS));mpoint[0].x = point.x;mpoint[0].y = point.y;mcpoint[0] = mcpoint[1] = point;SetCapture();break;
鼠标移动事件处理程序代码:
case 3: //draw circleif (bFlag){CBrush br(NULL_BRUSH);CBrush* oldBrush = pDC->SelectObject(&br);pDC->SetROP2(R2_NOT);DrawCircle(pDC, mcpoint[0], mcpoint[1]);pDC->SetROP2(R2_NOT);DrawCircle(pDC, mcpoint[0], point);mcpoint[1] = point;pDC->SelectObject(oldBrush);}break;
鼠标左键抬起事件处理程序代码:
case 3: //draw circleif (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();CBrush br(NULL_BRUSH);CBrush* oldBrush = pDC->SelectObject(&br);pDC->SetROP2(R2_NOT);DrawCircle(pDC, mcpoint[0], mcpoint[1]);pDC->SelectObject(oldBrush);int radii = sqrt(pow(mcpoint[1].x - mcpoint[0].x, 2) + pow(mcpoint[1].y - mcpoint[0].y, 2));circle(dst[1], mpoint[0], radii, Scalar(GetBValue(cCurrentColor), GetGValue(cCurrentColor), GetRValue(cCurrentColor)));::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));pDC->SetROP2(R2_COPYPEN);mString = "“画圆”操作已完成。";mInformation.SetWindowTextW(mString);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();}else{mString = "没发现打开的图像,“画圆”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}break;
下面分别试一下画直线、画圆、画椭圆、画矩形、画指引线的效果:
原始图如下:
为便于查看,先将当前色设置为红色(注意灰度图只能画黑白线条·),如下:
画图后的效果如下:
目前这个程序所有的绘图都是直接画在图片上,一旦完成就没法修改。要想改动只有返回重画。其实可以把所有画图都由MFC完成,将其存在容器中,这样就可以修改了。
7 放置文字
放置文字,首先需要输入文字,让后才能将文字放置到图片上。这里,动态创建一个MFC编辑框控件,用来输入文字,让后用OpenCV的putText函数将文字放置到图片上。下面看下程序代码。
按钮事件处理程序代码:
void CEasyImageDlg::OnBnClickedText()
{// TODO: 在此添加控件通知处理程序代码iFlag = 7;mString = "开始“放置文字操作”,请将鼠标指针移动到要放在文字位置,点击左键,然后输入要添加的文字,输入完成后,请再次点击鼠标左键。如需中途取消,请按Esc键。";mInformation.SetWindowTextW(mString);
}
左键按下事件处理程序代码:
case 7: //put textpWnd = GetDlgItem(1234);LOGFONT logfont;CFont* pFont;{if (bFlag != true){bFlag = true;CClientDC dc(this);pFont = dc.GetCurrentFont();pFont->GetLogFont(&logfont);pWnd->MoveWindow(point.x, point.y, point.x + logfont.lfWidth, point.y + logfont.lfHeight);pWnd->ShowWindow(SW_SHOW);pWnd->SetFocus();SetCaretPos(point);ShowCaret();mpoint[0].x = point.x;mpoint[0].y = point.y;mcpoint[0] = mcpoint[1] = point;}else{if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1]);iCount = matContainer.size();bFlag = false;pWnd->GetWindowTextW(mString);pWnd->ShowWindow(SW_HIDE);pWnd->MoveWindow(0, 0, 0, 0);m_str = CT2A(mString);int fontFace = FONT_HERSHEY_SIMPLEX;putText(dst[1], m_str, mpoint[0], fontFace, 1, Scalar(GetBValue(cCurrentColor), GetGValue(cCurrentColor), GetRValue(cCurrentColor)),2);iFlag = 0;mString = "“放置文字”操作已完成。";mInformation.SetWindowTextW(mString);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();}else{mString = "没发现打开的图像,“放置文字”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}}break;
下面看下放置文字的效果:
8 退出
退出即退出程序,此按钮的事件处理程序代码很简单。直接调用OnCancel()函数即可。
9 用当前色替换背景2
用当浅色替换背景,是用OpenCV的grabCut函数实现前景与背景分离,当前景与背景差别较大时,可实现一键抠图。按钮事件处理程序代码如下;
void CEasyImageDlg::OnBnClickedRemoveBgnd2()
{mString = "选择的操作是“用当前色替换背景2”,请先画一个选择框,并确保选择框中主要是前景物,且不要超出图像边界。如果不存在选择框,该操作将退出。";mInformation.SetWindowTextW(mString);if (!bDrawFrame){SetTimer(1, 3000, NULL);}else{ if (pickFrame.m_rect.right > dst[1].cols || pickFrame.m_rect.bottom > dst[1].rows){mString = "选择框超出了图像边界,请将选择框调整到图像边界以内,“用当前色替换背景2”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}else{mString = "“用当前色替换背景2”操作进行中,请稍候!";mInformation.SetWindowTextW(mString);Invalidate();if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();Mat result, bgModel, fgModel;grabCut(dst[1], result, Rect(pickRect.left, pickRect.top, pickRect.Width(), pickRect.Height()), bgModel, fgModel, 5, GC_INIT_WITH_RECT);compare(result, GC_PR_FGD, result, CMP_EQ);Mat foreground(dst[1].size(), CV_8UC3, Scalar(GetBValue(cCurrentColor), GetGValue(cCurrentColor), GetRValue(cCurrentColor)));dst[1].copyTo(foreground, result);dst[1] = foreground;mString = "“用当前色替换背景2”操作已完成!";mInformation.SetWindowTextW(mString);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objectInvalidate();}else{mString = "没有打开的图像,“用当前色替换背景2”以退出!";mInformation.SetWindowTextW(mString);Invalidate();}}}
}
下面看一些其运行效果。原始图如下:
绘制选择框后:
按下该按钮后的效果:
10 用当前色替换背景
用当前色替换背景,是用OpenCV floodFill函数实现的,需要输入一个上偏差在按钮的下面放了一个编辑框控件,用以输入数据。看下程序代码。 按钮事件处理代码:
void CEasyImageDlg::OnBnClickedRemoveBgnd1()
{iFlag = 1;mString = "您选取的是“用当前色替换背景”操作,请先在去背景1按钮旁输入适当的偏差值(如果不想输入就保留缺省值),然后将鼠标指针移动到要去除的背景区,再按鼠标左键";mInformation.SetWindowTextW(mString);
}
鼠标左键按下事件处理程序代码:
case 1:iFlag = 0;mpoint[0].x = point.x;mpoint[0].y = point.y;if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1]);iCount = matContainer.size();UpdateData(1);floodFill(dst[1], mpoint[0], Scalar(GetBValue(cCurrentColor), GetGValue(cCurrentColor), GetRValue(cCurrentColor)), 0, Scalar(iDiffernce, iDiffernce, iDiffernce), Scalar(iDiffernce, iDiffernce, iDiffernce), FLOODFILL_FIXED_RANGE);medianBlur(dst[1], dst[1], 5);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();mString = "“用当前色替换背景”操作已完成";mInformation.SetWindowTextW(mString);}else{mString = "没发现打开的图像,“用当前色替换背景”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}break;
下面看下运行效果,打开原图像如下:
点击用当前色替换背景按钮,在输入框中输入40,点击左上方背景处,结果如下:
再点击按钮,点击右上方,结果如下:
再点击按钮,点击左下方,结果如下:
同样也实现了抠图。
11 缩放
图像缩放需要输入缩放系数,故在缩放按钮下面放了一个编辑框控件。这里用OpenCV的resize函数实现,缩放按钮的事件处理程序的代码如下:
void CEasyImageDlg::OnBnClickedScale()
{if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();UpdateData(1);int nWidth, nHeight;nWidth = (int)(fScale * dst[1].cols) / 4 * 4;nHeight = fScale * dst[1].rows;resize(dst[1], dst[1], Size(nWidth, nHeight));dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();mString.Format(L"“缩放”操作已完成,现在图像大小是缩放前的 %f 倍", fScale);mInformation.SetWindowTextW(mString);}else{mString = "没发现打开的图像,“缩放”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}
现在来测试下效果,打开图像原始状态如下:
在输入框中输入:0.5, 然后点击缩放按钮,结果如下:
图像已经缩小。再在输入框中输入1.5,让后点击缩放按钮,结果如下:
图像放大了。
12 亮度调节
亮度调节,使较暗的图片变亮。这里用OpenCV的convertTo函数来实现,亮度调节按钮的事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedBrightness()
{// TODO: 在此添加控件通知处理程序代码if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();UpdateData(1);dst[1].convertTo(dst[1], -1, 1.0, iBrightness);//adjust brightness by offset value +/-mString = "你选择的是“亮度调节”操作,如果你想增加亮度,请在先旁边输入框中输入一个正整数,反之,则输入一个负整数。“亮度调节”操作已完成。";mInformation.SetWindowTextW(mString);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();}else{mString = "没发现打开的图像,“亮度调节”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}
下面试下效果,打开的原始图像如下:
人面比较暗,在亮度调节按钮下面的输入框中输入:30,然后点击亮度调节按钮,结果如下:
再在输入框中输入:-30,点击亮度调节按钮,结果如下:
亮度又变暗了。
13 对比度调节
对比度不打的图片,增加对比度可是图片变得更清晰。这里也是用OpenCV的convertTo函数来实现,对比度度调节按钮的事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedContrast()
{// TODO: 在此添加控件通知处理程序代码if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();UpdateData(1);dst[1].convertTo(dst[1], -1, fContrast, 0);mString = "你选择的是“对比度调节”操作,如果你想增加对比度,请在先旁边输入框中输入一个大于1的数,反之,则输入一个小于1的数。对比度调节操作已完成。";mInformation.SetWindowTextW(mString);dst[0] = dst[1];MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();}else{mString = "没发现打开的图像,“对比度调节”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}
}
测试一下效果,在输入框中输入1.3,然后点击对比度调节按钮,结果如下:
可见图像变得更清晰了。
14 旋转图像
旋转图像可将图像沿逆时针方向或顺时针反向旋转一定角度。这里用OpenCV的warpAffine函数来实现。旋转图像代码如下:
void CEasyImageDlg::RoateImage(Mat& src, Mat& dst, float angle)
{Mat rmat;if (src.data)rmat = getRotationMatrix2D(Point2f(src.cols / 2, src.rows / 2), angle, 1.0);float fcos = abs(rmat.at<double>(0, 0));float fsin = abs(rmat.at<double>(0, 1));int inWidth = fcos * src.cols + fsin * src.rows;int inHeight = fsin * src.cols + fcos * src.rows;rmat.at<double>(0, 2) += (inWidth / 2 - src.cols / 2);rmat.at<double>(1, 2) += (inHeight / 2 - src.rows / 2);warpAffine(src, dst, rmat, Size(inWidth, inHeight), INTER_LINEAR, BORDER_REPLICATE, Scalar(255, 255, 255));
}
//End of RoateImage(Mat& src, Mat& dst, float angle)
旋转图像按钮事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedRotate()
{if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();UpdateData(1);fGlobalAngle += fAngle;RoateImage(dst[0], dst[1], fGlobalAngle);mString = "“旋转图像”操作已完成。如果你想逆时针旋转图像,请在先旁边输入框中输入一个正数,反之,则输入一个负数。";mInformation.SetWindowTextW(mString);MatToCImage(dst[1], mImage); //send Mat object data to CImage objectInvalidate();}else{mString = "没发现打开的图像,“旋转图像”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}
下面试下效果。打开图像,在旋转图像按钮下面的输入框中输入:20,
点击旋转图像按钮,结果如下:
15 用当前色填充选择区域
局部填充可以掩盖不想让别人看到的内容,这里用OpenCV的ROI区域填充来实现。用当前色填充选择区域按钮的事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedFillPickrec()
{if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();if (pickFrame.m_rect.right > dst[1].cols || pickFrame.m_rect.bottom > dst[1].rows){mString = "选择框超出了图像边界,请将选择框调整到图像边界以内,“用当前色填充选择区”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}else{dst[1](Rect(pickRect.left, pickRect.top, pickRect.Width(), pickRect.Height())) = Scalar(GetBValue(cCurrentColor), GetGValue(cCurrentColor), GetRValue(cCurrentColor));dst[0] = dst[1];fGlobalAngle = 0;mString = "“用当前色填充选择区域”操作已完成!";mInformation.SetWindowTextW(mString);MatToCImage(dst[1], mImage);OnBnClickedDelPickFrame(); //call delete pick frame operation function}}else{mString = "没发现打开对的图像,“用当前色填充选择区域”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}
下面看下效果,打开图像,设置当前色为红色,并绘制选择框,如下:
点击用当前色填充选择区域按钮,结果如下:
16 保留选择区域内图像
保留选择区域内图像,即只保留图像的选择区域内的部分图像,其余舍去,这样可实现截图。保留选择区域内图像按钮事件处理代码如下:
void CEasyImageDlg::OnBnClickedKeepPickrec()
{if (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();if (pickFrame.m_rect.right > dst[1].cols || pickFrame.m_rect.bottom > dst[1].rows){mString = "选择框超出了图像边界,请将选择框调整到图像边界以内,“保留选择区域内图像”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}else{Mat mTem = dst[1];if (bDrawFrame){dst[1] = mTem(Rect(pickRect.left, pickRect.top, pickRect.Width(), pickRect.Height()));dst[0] = dst[1];fGlobalAngle = 0;MatToCImage(dst[1], mImage);OnBnClickedDelPickFrame();mString = "“保留选择区域内图像”操作已完成!";mInformation.SetWindowTextW(mString);}else{mString = "没发现选择框,请绘制选择框后在执行该操作!";mInformation.SetWindowTextW(mString);}}}else{mString = "没发现打开对的图像,“保留选择区域内图像”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}}
下面看下效果。打开图像,并绘制选择框,如下:
点击保留选择区域内图像按钮,结果如下:
17 选择区域磨皮
选择区域磨皮,即只在选择范围内执行磨皮操作,可以避免因磨皮操作使图面变得不清晰。其主要代码与磨皮代码一致,这里就不列出来了。下面试下效果。打开图像如下:
绘制选择框,并执行选择区域磨皮,根据斑点的状况决定磨皮次数,移动选择框,继续执行这一动作,最后结果如下:
明显的斑点去除了,图像并没变得模糊,效果比前面胡磨皮操作好。
18 复制选择内容到剪贴板
复制剪贴区域到剪贴板用于与其他软件交互,如将编辑后的图片粘贴到Word中。其实现原理是将选择框中胡显示内容复制到剪贴板中,复制选择内容到剪贴板按钮的事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedCopytoClipboard()
{int width;int height;if (!bDrawFrame)MessageBox(L"还没有选中复制区域,请绘制选择框后再执行该操作!");else{width = pickRect.Width();height = pickRect.Height();OnBnClickedDelPickFrame();Invalidate();}HDC hScreenDC = GetDC()->GetSafeHdc();HDC hMemoryDC = CreateCompatibleDC(hScreenDC);HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);HGDIOBJ old_obj = SelectObject(hMemoryDC, hBitmap);// 裁剪屏幕图像到选择区域 BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, pickRect.left, pickRect.top, SRCCOPY);DeleteDC(hMemoryDC); // 释放屏幕DC// 将图像放入剪贴板 if (OpenClipboard()) // 使用当前窗口句柄 {EmptyClipboard();SetClipboardData(CF_BITMAP, hBitmap); // CF_BITMAP表示位图格式 CloseClipboard();}SelectObject(hMemoryDC, old_obj);DeleteDC(hMemoryDC);mString = "“复制选择内容到剪贴板”操作已完成!";mInformation.SetWindowTextW(mString);Invalidate();
}
下面看下效果,先打开word,这里新建一个word文档如下:
运行程序,并打开一张图片,并绘制选择框。如下:
点击复制选择框内容到剪贴板按钮。并切换到word,按鼠标右键,在弹出菜单中点击粘贴选项下面的粘贴图像图标,如下:
结果下:
说明已将选择内容复制到了剪贴板。
19 复制粘贴并模糊边界
复制粘贴并模糊边界,即复制图像中的某个区域内容去覆盖图像中另外一部分内容,并使粘贴痕迹不太明显。复制粘贴并模糊边界按钮事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedCopyPasteBlurBound()
{if (!bDrawFrame){mString = "没有选择框,无法确定复制区域,该指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}else{iFlag = 11;mString = "将执行“复制粘贴并模糊边界”指令,如果要退出该指令,请按ESC;继续,请点击要粘贴的中心位置!";mInformation.SetWindowTextW(mString);pickRect = pickFrame.m_rect;dst[3] = dst[1](Rect(pickRect.left, pickRect.top, pickRect.Width() / 4 * 4, pickRect.Height())).clone();}
}
要实现该操作还需出来鼠标事件,鼠标左键按下处理程序代码如下:
case 11: //copy paste & bound blurif (dst[1].data){if (matContainer.size() >= 5)matContainer.pop_front();matContainer.push_back(dst[1].clone());iCount = matContainer.size();int dl, dr, dt, db;dl = point.x - dst[3].cols / 2;dr = dst[1].cols - (point.x + dst[3].cols / 2);dt = point.y - dst[3].rows / 2;db = dst[1].rows - (point.y + dst[3].rows / 2);if (dl < 5 || dr < 5 || dt < 5 || db < 5){mString = "选择粘贴区域中心位置不合适,导致粘贴区域超出图像的最大边界,“复制粘贴并模糊边界”操作将不再执行。";mInformation.SetWindowTextW(mString);}else{dst[3].copyTo(dst[1](Rect(point.x - dst[3].cols / 2, point.y - dst[3].rows / 2, dst[3].cols, dst[3].rows)));Rect rec1(point.x - dst[3].cols / 2 - 5, point.y - dst[3].rows / 2 - 5, dst[3].cols + 5, dst[3].rows + 5);Rect rec2(point.x - dst[3].cols / 2 + 5, point.y - dst[3].rows / 2 + 5, dst[3].cols - 5, dst[3].rows - 5);Mat mt1 = dst[1](rec1).clone();Mat mt2 = dst[1](rec2).clone();for (int i = 0; i < 3; i++){GaussianBlur(mt1, mt1, Size(5, 5), 0, 0);}mt1.copyTo(dst[1](rec1));mt2.copyTo(dst[1](rec2));mString = "“复制粘贴并模糊边界”操作已完成。";mInformation.SetWindowTextW(mString);dst[0] = dst[1];fGlobalAngle = 0;MatToCImage(dst[1], mImage); //send Mat object data to CImage objiectInvalidate();}}else{mString = "没发现打开的图像,“复制粘贴并模糊边界”指令已退出!";mInformation.SetWindowTextW(mString);Invalidate();}break;
下面试下效果,绘制选择框,如下:
点击复制粘贴并模糊边界按钮,再仔猫身上选择一个点,点击鼠标左键,结果如下:
由于选择区域与粘贴区域差别太大,边界还是有些明显。需用选择区域磨皮处理一下,看起来才会自然一些。处理后如下:
20 复制粘贴
复制粘贴与复制粘贴并模糊边界二者作用大致相同,其代码也大致相同这里就不做介绍了。
21 删除选择框
删除选择框实际上是将选择框的尺寸设置为0,不再显示。其代码简单这里就不做介绍了。
22 绘制选择框
绘制选择框需用到MFC的CRectTracker类,前面专门介绍CRectTracker类的博文,这里就不做介绍了。
23 拾取颜色替换当前色
拾取颜色替换当前色是用MFC的GetPixel来获取选择点的RGB值,然后赋值给COLORREF变量。拾取颜色替换当前色按钮事件处理程序代码如下:
void CEasyImageDlg::OnBnClickedPickColor()
{iFlag = 8; //pick colormString = "你选择的是“拾取颜色替换当前色”操作,如果像取消请按Esc键";mInformation.SetWindowTextW(mString);
}
鼠标左键按下事件处理程序代码如下:
case 8: //pick color
{iFlag = 0;CDC* pdc = GetDC();cCurrentColor = pdc->GetPixel(point);iRed = GetRValue(cCurrentColor);iGreen = GetGValue(cCurrentColor);iBlue = GetBValue(cCurrentColor);UpdateData(0);pen.CreatePen(PS_SOLID, 1, cCurrentColor);ReleaseDC(pdc);mString = "“拾取颜色替换当前色”操作已完成。";mInformation.SetWindowTextW(mString);Invalidate();
}break;
试一下效果,点击拾取颜色替换当前色按钮,然后点击猫的眼睛,结果如下:
当前色指示器颜色及RGB值都更新成了猫的眼睛色。
24 设置当前色
设置当前色是通过MFC的原色对话框来实现的,前面有专门介绍MFC颜色对话框的博文,这里就不做介绍了。下面看一下设置当前色的操作。点击设置当前色按钮可打开颜色对话框。如下:
可从基本色中选择颜色,也可以自定义颜色,点击规定自定义颜色按钮,可选择更多的颜色,如下:
本程序的所有按钮都已介绍完毕。本示例的图片编译后的程序及源代码都已上传到CSDN,如需查看详细代码可以去下载。
可执行程序链接:https://download.csdn.net/download/billliu66/89619138
源代码链接:https://download.csdn.net/download/billliu66/89619147
相关文章:

用OpenCV与MFC写一个简单易用的图像处理程序
工厂里做SOP及测试报告以及员工资格鉴定等常需用到简单的图像处理,PS等软件正版费用不菲,学习起来成本也高。Windows自带的图像处理软件,用起来也不是那么得心应手。因此我用OpenCV与MFC写了一个简单易用的图像处理程序。 程序界面 基于简单…...

go语言的actor框架和air工具有什么区别?
Go语言的Actor框架和Air工具在多个方面存在显著的区别,主要体现在它们的设计目的、功能特性以及应用场景上。 ### Go语言的Actor框架 **设计目的与功能特性**: * **设计目的**:Actor框架是专为高并发和分布式系统设计的编程模型。它通过将系统…...

e6.利用 docker 快速部署自动化运维平台
利用 docker 快速部署自动化运维平台 1. 安装docker2. 拉取镜像3. 启动容器4. 初始化5. 访问测试 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台,整合了主机管理、主机批量执行、主 机在线终端、文件在线上传下载、应用发布部署、在线任务计划、配置中…...

回顾前面刷过的算法(4)
今天回顾一下下面三个算法,涉及到了动态规划、合并链表、位运算,好吧,让我们再次手敲一遍 //乘积最大子数组//思路: 维护三个变量,imax最大前缀乘积 imin最小前缀乘积 max最大连续乘积//由于元素有正负,imax和imin需…...

SourceTree配置多个不同Remote地址的仓库
需求 在我们开发过程中,有可能需要拉取的地址仓库不在同一个仓库中,有些可能在Github上,有些可能在Gitlab上。 所以我们需要配置Github的仓库的配置和Gitlab仓库的配置。 现在,我们来配置两个不同的仓库的地址。 假设…...

【Golang 面试 - 进阶题】每日 3 题(十三)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/UWz06 📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏…...

自定义线程池(二)
上节回顾 在上一节当中,已经实现了一个线程池,在本节当中,我们需要添加拒绝策略。这里使用到了策略模式的设计模式,因为拒绝策略是多种的,我们需要将这个权利下放给调用者(由调用者来指定我要采取哪种策略…...

【Linux】常见指令
目录 一、指令的理解二、Linux的目录结构三、XShell 下的热键四、shell命令以及运行原理五、Linux常见的指令汇总1. ls 指令1.1 常见的一些有关 ls 的别名1.2 隐藏文件或目录1.3 * 的匹配 2. pwd 指令3. cd 指令3.1 cd . . 指令 4. touch指令5. mkdir指令6. rmdir指令 &&am…...

uniapp自定义网格布局用于选择金额、输入框焦点事件以及点击逻辑实战
样式 <view class="withdraw-section"><text class="section-title">提现金额</text><view class="amount-options"><view v-for="(item, index) in list" :key="index" class="amount-opt…...

中小学创客室培养学生全面发展
随着时代的发展,教育也在飞速发展,教育决定着一个国家的未来,一个家庭的未来,一个人的未来,我国近年来大力鼓励科学教育,支持科学创新。因此,学校应该重视对学生的科学教育,尤其是处于思想启蒙阶…...

AI Agent智能体落地应用测试,一句话即可操控它执行工作
一、什么是Agent 在软件应用中,软件代理或智能代理,是一种能够自主执行任务或做出决策的计算机程序。它们可以用于自动化任务、个性化推荐、数据分析等,这一类的桌面应用称之为Agent。如Siri、Alexa、Google Assistant等,它们能够…...

免费的SD-WAN服务
SD-WAN,SASE,零信任是近年来比较火的概念,SD-WAN发展已经很久了,但是真正能够自主研发做SD-WAN的企业其实并不算太多。 比扬云的SD-WAN产品是自主研发的,可控性强,最重要的是具有免费版本,可以免…...

gradle安装及配置
文章目录 一、下载安装包二、解压文件三、环境变量配置四、验证安装结果五、配置国内源六、IDEA配置 一、下载安装包 从gradle官网下载安装包,官网地址为:https://gradle.org/releases/ 我们只需要下载编译好的文件即可。 二、解压文件 解压文件到指定…...

C-sharp-console-gui-framework:C#控制台应用程序的GUI框架
推荐一个.Net开源项目,方便我们基于控制台创建图形用户界面(GUI)应用程序。 01 项目简介 ConsoleGUI是一个简单的布局驱动.NET框架,用于创建基于控制台的GUI应用程序。 核心功能: **布局驱动:**与WPF或H…...

一文搞懂后端面试之MySQL MVCC【中间件 | 数据库 | MySQL | 隔离级别 | Read View】
为什么需要MVCC 锁本身就是用于并发控制的,那么为什么InnoDB还要引入MVCC,读写都加锁不就可以控制住并发吗? 锁确实可以,但是性能太差。如果是纯粹的锁,那么写和写、读和写、读和读之间都是互斥的。如果是读写锁&…...

Mysql执行计划(上)
1、执行计划的概念 执行计划是什么:使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。 作用:分析你的查询语句或是表结构的性能瓶颈 语法:Explain SQL语句 执行计划输出内容介绍&#…...

使用Python+moviepy截取音频片段
一、使用AudioFileClip对象的subclip函数,截取1秒至3秒的音频 from moviepy.editor import *auAudioFileClip("/home/Download/test.mp3") # 创建对象clipau.subclip(1,3) # 截取1秒至3秒的音频clip.write_audiofile("/home/Download/clip.mp3"…...

Java学习Day19
动态SQL语句标签 1.if 用于根据条件判断是否包括某段 SQL 代码 <if test"checktext !null and check !"> 2.<choose>, <when>, <otherwise>类似于 Java 的 switch 语句,用于在多个条件中选择一个。 <select id"getSt…...

8.达梦数据库常用SQL
文章目录 前言1. 服务器资源1.1 CPU使用率1.2 内存使用率 2 数据库实例管理2.1 查询版本号2.2 查询ini配置2.3 查询归档配置2.4 数据库实例初始化参数2.5 查看数据库信息2.6 查看数据库实例信息2.7 查看数据库实例信息2.8 查看授权信息2.9 查询页大小,字符集大小2.1…...

深入理解接口测试:实用指南与最佳实践(四)IHRM管理系统实战-项目分析
您好,我是程序员小羊! 前言 这一阶段是接口测试的学习,我们接下来的讲解都是使用Postman这款工具,当然呢Postman是现在一款非常流行的接口调试工具,它使用简单,而且功能也很强大。不仅测试人员会使用…...

程序编译及链接
你好!感谢支持孔乙己的新作,本文就程序的编译及链接与大家分析我的思路。 希望能大佬们多多纠正及支持 !!! 个人主页:爱摸鱼的孔乙己-CSDN博客 1.翻译译环境与运行环境 当我们进行程序设计时&…...

route 命令介绍及使用方法
route 命令 作用:用于显示和操作 IP 路由表 (show/manipulate the IP routing table)。 在命令行下执行 route 命令添加路由,不会永久保存,当网卡重启或者机器重启后,该路由就会失效。 命令参数…...

力扣热题100_二叉树_226_翻转二叉树
文章目录 题目链接解题思路解题代码 题目链接 226. 翻转二叉树 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1] 示例 2: …...

Java SpringBoot 集成 MinIO 资料
Java SpringBoot 集成 MinIO 资料 一、文档 官方文档CSDN项目示例解除Spring文件上传大小限制 二、个人实战 注意事项: 部署MinIO时会涉及到两个端口号,一个为endpoint的端口,一个为console的端口,注意不要弄混 比如:…...

鸿蒙系统开发【加解密算法库框架】安全
加解密算法库框架 介绍 本示例使用ohos.security.cryptoFramework相关接口实现了对文本文件的加解密、签名验签操作。 实现场景如下: 1)软件需要加密存储本地文本文件,需要调用加解密算法库框架选择密钥文本文件,对本地文本文…...

C语言——二维数组和字符数组
二维数组 二维数组本质上是一个行列式的组合,也就是二维数组是有行和列两部分构成。二维数组数据是通过行列进行解读。 定义形式: 类型(说明符) 数组名[行数(常量表达式1)][列数(常量表达式…...

Python 爬虫入门(九):Scrapy安装及使用「详细介绍」
Python 爬虫入门(九):Scrapy安装及使用「详细介绍」 前言1. Scrapy 简介2. Scrapy 的安装2.1 环境准备2.2 安装 Scrapy 3. 创建 Scrapy 项目3.1 创建项目3.2 项目结构简介 4. 编写爬虫4.1 创建爬虫4.2 解析数据4.3 运行爬虫 5. 存储数据5.1 存…...

扩展addr2line程序的功能,group_add2line() 脚本的实现
------------------------------------------------------------ author: hjjdebug date: 2024年 08月 05日 星期一 16:19:07 CST descrition: 扩展addr2line程序的功能,group_add2line() 脚本的实现 ------------------------------------------------------------ 扩展addr2…...

idea中修改项目名称
公司最近有个小项目新加了很多功能,在叫原先的项目名有点不合适了。所以在网上查了下资料,发现步骤都比较复杂。自己研究了一下找到了一个相对简单的方法,只需要两步,特此记录一下。 1.修改项目文件夹名称 关闭当前项目ÿ…...

Flink开发语言使用Java还是Scala合适?
目录 1. Flink简介 1.1 什么是Apache Flink? 1.2 Flink的核心组件 2. Java与Scala在Flink开发中的比较 2.1 语言特性对比 2.2 开发体验对比 3. 实际开发中的应用 3.1 使用Java进行Flink开发 3.2 使用Scala进行Flink开发 4. 关键性能和优化 4.1 性能对比 …...