用OpenCV与MFC写一个简单易用的图像处理程序

news2024/11/23 22:30:06

        工厂里做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 objiect
			Invalidate(); //fresh screen & display image
			mString.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 objiect
	Invalidate();

	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 objiect
		Invalidate();

		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 objiect
		Invalidate();

		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 flag
	mString = "开始“画圆”操作,请将鼠标指针移动到适当位置,按下鼠标左键不松,拖动鼠标,在适当位置松开鼠标左键,完成画圆。如需中途取消,请按Esc键。";
	mInformation.SetWindowTextW(mString);
}

鼠标左键按下事件处理函数,代码:

case 3://draw circle
	bFlag = 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 circle
	if (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 circle
	if (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 objiect
		Invalidate();
	}
	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 text
	pWnd = 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 objiect
				Invalidate();
			}
			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 object
				Invalidate();
			}
			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 objiect
		Invalidate();
		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 objiect
		Invalidate();

		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 objiect
		Invalidate();
	}
	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 objiect
		Invalidate();
	}
	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 object

		Invalidate();
	}
	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 blur
	if (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 objiect
			Invalidate();
		}
	}
	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 color
	mString = "你选择的是“拾取颜色替换当前色”操作,如果像取消请按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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

书生大模型学习笔记1 - 连接云端开发机

申请InternStudio开发机&#xff1a; 这里进去报名参加实战营即可获取 书生大模型实战营 InternStudio平台 创建开发机 SSH连接开发机&#xff1a; SSH免密码登录 本地创建SSH密钥 ssh-keygen -t rsa打开以下文件获取公钥 ~/.ssh/id_rsa.pub去InternStudio添加公钥 …

【iOS】—— autoreleasePool以及总结

autoreleasePool以及总结 1. 什么是autoreleasePool2. autoreleasePoolPageobjc_autoreleasePoolPush方法&#xff1a;objc_autoreleasePoolPop方法&#xff1a;tokenkill()方法 3. 总结3.1 autoreleasePool的原理3.2 autoreleasePool的问题3.2.1 autoreleasepool的嵌套操作3.2…

进程学习

今天开始了进程的学习&#xff0c;主要讲了进程的一些命令以及进程的创建、进程的调度、进程相关的函数接口和进程消亡。在函数接口中要注意子进程和父进程的关系以及回收顺序&#xff0c;避免出现僵尸进程。

C基础练习(学生管理系统)

1.系统运行&#xff0c;打开如下界面。列出系统帮助菜单&#xff08;即命令菜单&#xff09;&#xff0c;提示输入命令 2.开始时还没有录入成绩&#xff0c;所以输入命令 L 也无法列出成绩。应提示“成绩表为空&#xff01;请先使用命令 T 录入学生成绩。” 同理&#xff0c;当…

【为什么不要买运营商的机顶盒?解锁智能电视新体验,从一台刷机机顶盒开始】

【置顶:机顶盒刷机步骤请跳转此链接】 在这个数字化飞速发展的时代&#xff0c;电视早已不再是单一的播放工具&#xff0c;它正逐步演变成为家庭娱乐与信息获取的综合中心。然而&#xff0c;许多家庭在选择机顶盒时&#xff0c;往往会因为惯性或便利而直接选择运营商提供的机顶…

基于Tasking编译器AURIX TC3xxDemo工程创建

1、示例代码获取 1.1 英飞凌AURIX系列示例代码路径 Infineon/AURIX_code_examples: This repository contains code example projects for the AURIX™ Development Studio. (github.com) 1.2 代码获取 源代码获取有两种方式&#xff0c;一种通过git拉到本地仓库&#xff0…

修改微信(3.9.10.19版本)系统托盘图标(傻瓜教程)

微信版本&#xff1a; 进行以下操作先退出微信 1.iconfont Logo下载一个图标png&#xff0c;大小为256像素&#xff0c;前面颜色自己看着弄 2.png转ico,转化链接&#xff08;转化的网站很多不一定非要是这个&#xff09; 3.下载后续所需程序&#xff08;ResHacker和IconWo…

【动态规划】力扣918. 环形子数组的最大和

给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n] &#xff0c; nums[i] 的前一个元素是 nums[(i - 1 n) % n] 。 …

Jmeter性能压测4000并发

性能测试的底层逻辑 程序为什么会有性能问题 用户操作 客户端&#xff08;web/app/小程序&#xff09;触发网络请求&#xff0c;服务器处理大量网络请求代码运行需要大量服务器资源&#xff08;CPU、内存、网络、磁盘等等&#xff09; 资源不是无限&#xff0c;硬件配置不是随…

python-分享篇-英文短文自动分词写入文本文件

文章目录 准备代码效果 准备 代码 import string f open(./data/split.txt) sf.read() str1s.title() print(str1) print("".join([s for s in str1.splitlines(True) if s.strip()])) list1 str1.split() # 采用默认分隔符进行分割 #字符串列表去重 l1list(set(l…

lvs的dr模式综合实践

目录 ​编辑虚拟机准备工作 ​编辑​编辑​编辑 配置过程 配置client主机 配置router主机 配置lvs主机&#xff08;vip使用环回来创建&#xff09; 配置server1主机&#xff08;vip使用环回来创建&#xff09; 配置server2主机&#xff08;vip使用环回来创建&#xff0…

SpringBoot+Mybatis 分页

无论多数据源,还是单数据源,分页都一样,刚开始出了点错,是因为PageHelper的版本问题 这里用的SpringBoot3 SpringBoot2应该是没有问题的 相关代码 dynamic-datasourceMybatis多数据源使用-CSDN博客 依赖 <?xml version"1.0" encoding"UTF-8"?&g…

Maven+Tomcat环境搭建

Maven Maven框架的作用 1.在JavaWeb开发中需要使用大量的jar包,这些jar包需要手动导入 2.自动导入和配置jar包 Maven项目架构管理工具 方便导入jar包 Maven的核心思想:约定大于配置 有约束不要去违反 Maven会规定好你该如何去编写java代码,必须按照规范来 安装流程 1…

Godot学习笔记8——PONG游戏制作

目录 一、小球 二、地图 三、积分系统 四、玩家场景 五、导出与发布 PONG是1972年由雅达利公司推出的游戏&#xff0c;主要玩法为玩家控制两个可以上下移动的板子击打屏幕中不断运动的球 一、小球 我们首先创建一个“Area2D”场景&#xff0c;在它下方创建“Collisi…

好书推荐|复旦大学专家团队著《大规模语言模型:从理论到实践》(附PDF)

前言 在这个迅猛发展且复杂多变的技术领域&#xff0c;掌握大型模型的理论核心并有效地将其应用于实践&#xff0c;对许多人来说是一项艰巨的任务。为此&#xff0c;《大规模语言模型&#xff1a;从理论到实践》一书应运而生。本书由复旦大学计算机科学技术学院知名教授张奇领…

将增强型乳腺摄影添加到断层合成中用于有乳腺癌个人病史女性的乳腺癌检测:| 文献速递-基于深度学习的乳房、前列腺疾病诊断系统

Title 题目 Addition of Contrast-enhanced Mammography to Tomosynthesis for Breast Cancer Detection in Women with a Personal History of Breast Cancer: 将增强型乳腺摄影添加到断层合成中用于有乳腺癌个人病史女性的乳腺癌检测&#xff1a; Background 背景 Dig…

【黄啊码】什么是SD?SD的使用技巧

目录 SD的简介 SD的优势 1、不需要绘画基础&#xff0c;便能绘制精美的图片&#xff0c;插画 2、StableDiffusion不仅用于静态图像生成&#xff0c;还可应用于动态图像和视频创作&#xff0c;拓宽了AI绘画的应用领域。 3、快速发展&#xff0c;成熟的社区——帮助使用者…

Linux系统 腾讯云服务/宝塔面板安装《最新版本2024》禅道开源版本20.2

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 有两种方式1.自带有服务器安装和2.使用禅道官方的服务器免费使用 第一种&#xff1a;免费的提供5人使用&#xff0c;存储的数据大小也是有限制的范围的 禅道下载 - 禅道项目管理软件 下滑页面就能…

大事件前端篇JavaScript导入导出

前置知识&#xff1a; 呃呃呃......有那么一点点&#xff1f;&#xff1f;但不多。。。。不管&#xff0c;先学&#xff0c;不会的时候再去看响应的知识点吧。 补充一个模块化相关的知识点&#xff1a;JavaScript-导入导出 JS提供的导入导出机制&#xff0c;可以实现按需导…

尚品汇-创建ES索引库(二十七)

目录&#xff1a; &#xff08;1&#xff09;商品检索功能介绍 &#xff08;2&#xff09;根据业务搭建数据结构 &#xff08;3&#xff09;nested 介绍 &#xff08;4&#xff09;搭建service-list服务 &#xff08;5&#xff09;构建实体与es mapping建立映射关系 &…