一、总体效果及功能介绍
1、总体效果呈现
I、刚运行运行打开时的界面
II、添加一张图片后
III、对各个按键的简单介绍
IV、选取小区域时会有矩形边框,坐标跟踪鼠标实时显示
V、添加涂鸦,可选粗细、颜色
VI、添加模糊滤镜
VII、镜像
2、功能介绍
打开、显示图片
首先在OnInitDialog()函数中设置显示图片的窗口,定义一个指向 CWnd 类型的指针变量pWnd1,用于存储获取到的控件的指针,利用GetDlgItem(IDC_STATIC)函数获取对应Picture Control控件的指针,用pWnd1->GetClientRect(&rect1)获得控件自身的坐标大小,用namedWindow("img", WINDOW_AUTOSIZE)设置窗口名;然后在OnBnClickedButton1()函数中用 CFileDialog dlg(TRUE, _T(".png"), _T("psa"), OFN_HIDEREADONLY |OFN_OVERWRITEPROMPT, _T("位图文件(*.png)|*.png|JPEG文件(*.jpg)|*.jpg||"))创建文件对话框并用CString imagePath = dlg.GetPathName()获取选择的文件路径;最后先用m_srcImg = imread(imagePathStr)读取图像文件,再用resize(m_srcImg, m_srcImg, Size(rect1.Width(), rect1.Height()))调整图片大小和控件框相同,然后用imshow("img", m_srcImg)显示图像在窗口img中。
核心源代码:
OnInitDialog()函数中部分:
CWnd* pWnd1 = GetDlgItem(IDC_STATIC);
pWnd1->GetClientRect(&rect1);
namedWindow("img", WINDOW_AUTOSIZE);
HWND hWnd1 = (HWND)cvGetWindowHandle("img"); HWND hParent1 = ::GetParent(hWnd1);
::SetParent(hWnd1, GetDlgItem(IDC_STATIC)->m_hWnd);
::ShowWindow(hParent1, SW_HIDE);
OnBnClickedButton1()函数中部分:
CFileDialog dlg(TRUE, _T(".png"), _T("psa"), OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT, _T("位图文件(*.png)|*.png|JPEG文件(*.jpg)|*.jpg||"));
if (dlg.DoModal() == IDOK)
{
CString imagePath = dlg.GetPathName();
CStringA imagePathA(imagePath);
std::string imagePathStr(imagePathA.GetString());
m_srcImg = imread(imagePathStr);
resize(m_srcImg, m_srcImg, Size(rect1.Width(), rect1.Height()));
imshow("img", m_srcImg);
}
保存图片
首先创建一个文件对话框 CFileDialog,用于让用户选择保存图像的文件路径和文件名,对话框设置为保存模式 (FALSE),初始文件名为 ".png",默认文件名为 "psa",并且隐藏只读文件和覆盖提示;然后获取用户选择的文件路径和文件名,从图片框控件获取当前显示的图片;最后创建一个 CImage 对象 image,其大小与图片框控件的大小相同,使用 BitBlt() 将图片框控件中的内容复制到 image 中,使用 image.Save(imagePath) 将图像保存到用户选择的文件路径中。
核心源代码:
CFileDialog dlg(FALSE, _T(".png"), _T("psa"), OFN_HIDEREADONLY |OFN_OVERWRITEPROMPT, _T("位图文件(*.png)|*.png|JPEG文件(*.jpg)|*.jpg||"));
if (dlg.DoModal() == IDOK)
{
CString imagePath = dlg.GetPathName();
CStringA imagePathA(imagePath);
std::string imagePathStr(imagePathA.GetString());
CWnd* pWnd = GetDlgItem(IDC_STATIC);
CDC* pDC = pWnd->GetDC();
CRect rect;
pWnd->GetClientRect(&rect);
CImage image;
image.Create(rect.Width(), rect.Height(), 24);
CDC* pImageDC = CDC::FromHandle(image.GetDC());
pImageDC->BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
image.Save(imagePath);
}
亮度调整
亮度调整借助opencv提供的convertTo()函数完成。
convertTo() 函数的模板定义为:
void convertTo(OutputArray dst, int dtype, double alpha = 1, double beta = 0);
参数说明:
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量,该参数用于存储转换后的图像;
dtype:表示目标图像的数据类型,用于指定输出图像的像素类型;
alpha:可选参数,表示缩放因子。默认值为 1,表示不进行缩放。如果指定一个非零值,会将图像的像素值乘以该因子进行缩放;
beta:可选参数,表示偏移量。默认值为 0,表示不进行偏移。如果指定一个非零值,会将图像的像素值加上该偏移量;
作用介绍:
这个函数用于将图像的像素值转换为指定的数据类型,并可以通过乘以缩放因子和加上偏移量来调整像素值的范围和亮度。常见的用法是将图像从一个数据类型转换为另一个数据类型,或者将像素值从一个范围调整到另一个范围。
本程序用adjustedImg.convertTo(adjustedImg, -1, 1, (brightnessValue * 2.55 - static_cast<double>(100)))这行代码进行亮度调整,将图像 adjustedImg 的亮度根据 brightnessValue 进行调整,并将调整后的结果保存回 adjustedImg 中。
核心源代码:
adjustedImg.convertTo(adjustedImg, -1, 1, (brightnessValue * 2.55 - static_cast<double>(100)));
饱和度调整
首先创建一个临时的 HSV 图像容器,将要调整的图像 adjustedImg 借助opencv提供的cvtColor()函数从 BGR 颜色空间转换为 HSV 颜色空间,并将结果保存在 hsvImg 中;然后根据滑块的位置 saturationValue,计算一个饱和度调整因子,遍历 HSV 图像的每个像素,根据饱和度调整因子,对当前像素的饱和度通道进行调整,使用 saturate_cast 来确保调整后的值在合法范围内;最后将调整后的 HSV 图像 hsvImg 转换回 BGR 颜色空间,并将结果保存在 adjustedImg 中,以便后续显示和处理。
cvtColor() 函数的模板定义为:
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn = 0);
参数说明:
src:表示输入图像的对象,通常是一个 cv::Mat 类型的变量。该参数是待转换的图像。
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于存储转换后的图像。
code:表示颜色空间转换的代码。可以使用 OpenCV 提供的颜色空间转换常量,如 cv::COLOR_BGR2HSV、cv::COLOR_RGB2HSV 等。在这里,cv::COLOR_BGR2HSV 表示将 BGR 颜色空间转换为 HSV 颜色空间。
dstCn:可选参数,表示输出图像的通道数。默认值为 0,表示与输入图像的通道数相同。
作用介绍:
这个函数用于在不同的颜色空间之间进行转换。它可以将图像从一个颜色空间转换为另一个颜色空间,或者在同一颜色空间内进行通道的重新排列。
核心源代码:
Mat hsvImg;
cvtColor(adjustedImg, hsvImg, cv::COLOR_BGR2HSV);
double saturationFactor = saturationValue / 35.0;
for (int y = 0; y < hsvImg.rows; ++y)
{
for (int x = 0; x < hsvImg.cols; ++x)
{
Vec3b& pixel = hsvImg.at<cv::Vec3b>(y, x);
pixel[1] = cv::saturate_cast<uchar>(pixel[1] * saturationFactor);
}
}
cvtColor(hsvImg, adjustedImg, cv::COLOR_HSV2BGR);
马赛克处理
首先根据滑块的位置 mosaicValue,确定马赛克的块大小。该值表示每个马赛克块的边长,计算调整后图像的宽度,将其分为块大小确定的块数,计算调整后图像的高度,将其分为块大小确定的块数;然后借助opencv提供的resize()函数将调整后的图像 adjustedImg 缩放为新的尺寸,以实现马赛克效果;最后显示调整后的图像。
resize() 函数的模板定义为:
void resize(InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR);
参数说明:
src:表示输入图像的对象,通常是一个 cv::Mat 类型的变量。该参数是待调整大小的图像。
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于存储调整大小后的图像。
dsize:表示输出图像的尺寸,通常使用 cv::Size 类型的对象来表示。可以指定目标图像的宽度和高度,也可以根据 fx 和 fy 参数来进行缩放。
fx:可选参数,表示在水平方向上的缩放因子。默认值为 0,表示不进行缩放。如果指定一个非零值,将根据该缩放因子对图像进行水平方向的缩放。
fy:可选参数,表示在垂直方向上的缩放因子。默认值为 0,表示不进行缩放。如果指定一个非零值,将根据该缩放因子对图像进行垂直方向的缩放。
interpolation:可选参数,表示插值方法用于调整图像大小。默认值为 INTER_LINEAR,表示双线性插值方法。其他常用的插值方法还有 INTER_NEAREST、INTER_CUBIC 等。
作用介绍:
这个函数用于缩放图像,可以通过指定输出图像的尺寸或缩放因子来将图像缩小或放大;也可以用于通过指定输出图像的尺寸来裁剪图像的部分区域,提取感兴趣的图像区域进行进一步处理。
核心源代码:
int blockSize = mosaicValue;
int smallWidth = adjustedImg.cols / blockSize;
int smallHeight = adjustedImg.rows / blockSize;
resize(adjustedImg, adjustedImg, cv::Size(smallWidth, smallHeight), 0, 0, INTER_NEAREST);
子区域选择
首先当鼠标左键按下时,记录下鼠标的坐标;然后当鼠标右键按下时,记录下鼠标的坐标;最后根据这两个坐标借助opencv提供的rectangle()画一个矩形。
rectangle() 函数的模板定义为:
img:表示输入/输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于指定要在其上绘制矩形的图像。
pt1:表示矩形的一个角点,通常使用 cv::Point 类型的对象来表示。该参数指定了矩形的左上角或右下角的点。
pt2:表示矩形的另一个角点,同样是一个 cv::Point 类型的对象。该参数指定了矩形的左上角或右下角的点,与 pt1 形成对角线。
color:表示矩形的颜色,使用 cv::Scalar 类型的对象表示。该参数指定了矩形的颜色,可以是灰度值或 RGB 值。
thickness:可选参数,表示矩形线条的粗细。默认值为 1,表示绘制实心矩形。如果指定一个大于 1 的值,表示绘制空心矩形,并指定线条的宽度。
lineType:可选参数,表示线条的类型。默认值为 LINE_8,表示 8 连通线条。其他选项包括 LINE_4 和 LINE_AA。
shift:可选参数,表示坐标点的小数位数。默认值为 0,表示使用整数坐标。
作用介绍:
这个函数用于在图像上标记或绘制感兴趣的区域,如物体检测、目标跟踪等应用场景。
核心源代码:
char temp[16];
if (event == CV_EVENT_LBUTTONDOWN)
{
Matstack.top().copyTo(img);
resize(img, img, Size(rect1.Width(), rect1.Height()));
sprintf_s(temp, "(%d,%d)", x, y);
pre_pt = Point(x, y); resize(img, img, Size(rect1.Width(),rect1.Height()));
}
else if (event == CV_EVENT_MOUSEMOVE && !(flags & CV_EVENT_FLAG_LBUTTON))
{
img.copyTo(tmp);
sprintf_s(temp, "(%d,%d)", x, y);
cur_pt = Point(x, y);
putText(tmp, temp, cur_pt, FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 220)); resize(tmp, tmp, Size(rect1.Width(), rect1.Height()));
imshow("img", tmp);
}
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
{
img.copyTo(tmp);
sprintf_s(temp, "(%d,%d)", x, y);
cur_pt = Point(x, y);
putText(tmp, temp, cur_pt, FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 220)); rectangle(tmp, pre_pt, cur_pt, Scalar(0, 255, 0), 1, 8, 0);
resize(tmp, tmp, Size(rect1.Width(), rect1.Height()));
imshow("img", tmp);
}
else if (event == CV_EVENT_LBUTTONUP)
{
sprintf_s(temp, "(%d,%d)", x, y);
cur_pt = Point(x, y);
putText(img, temp, cur_pt, FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 20)); circle(img, pre_pt, 2, Scalar(0, 0, 0), CV_FILLED, CV_AA, 0);
rectangle(img, pre_pt, cur_pt, Scalar(0, 0, 0), 0, 8, 0);
width = abs(pre_pt.x - cur_pt.x);
height = abs(pre_pt.y - cur_pt.y);
if (width == 0 || height == 0)
{
return;
}
roi = img(Rect(min(cur_pt.x, pre_pt.x), min(cur_pt.y, pre_pt.y), width, height)).clone();
Matstack.push(roi);
Matstack2.push(roi);
resize(roi, roi, Size(rect2.Width(), rect2.Height()));//自适应大小
imshow("img2", roi);
setMouseCallback("img", nullptr, 0);
}
撤销功能
首先定义一个Mat类的栈;然后将经过每次操作后的图片入栈;最后需要撤销时,将栈顶出栈,再显示栈顶图片即可。
核心源代码:
if (Matstack.size()>1)
{
Matstack.pop();
Mat b = Matstack.top();
resize(b, b, Size(rect1.Width(), rect1.Height()));
imshow("img", b);
}
if (Matstack.size() == 1)
{
resize(m_srcImg, m_srcImg, Size(rect1.Width(), rect1.Height())); imshow("img", m_srcImg);
}
涂鸦功能
首先声明并初始化一个静态的 Point 变量 prev_pt2,用于保存上一个鼠标位置;然后处理鼠标事件,如果鼠标左键按下,将当前鼠标位置保存到 prev_pt2 变量中,如果鼠标移动并且左键被按下,将当前鼠标位置保存到 cur_pt2 变量中,借助opencv提供的line()函数在图像org2上绘制连续线条;最后在窗口中显示更新后的图像。
line() 函数的模板定义为:
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
参数说明:
img:表示输入/输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于指定要在其上绘制直线的图像。
pt1:表示直线的起始点,通常使用 cv::Point 类型的对象来表示。该参数指定了直线的起始位置。
pt2:表示直线的结束点,同样是一个 cv::Point 类型的对象。该参数指定了直线的结束位置。
color:表示直线的颜色,使用 cv::Scalar 类型的对象表示。该参数指定了直线的颜色,可以是灰度值或 RGB 值。
thickness:可选参数,表示直线的粗细。默认值为 1,表示绘制一条单像素宽度的直线。如果指定一个大于 1 的值,表示绘制宽度为指定值的直线。
lineType:可选参数,表示线条的类型。默认值为 LINE_8,表示 8 连通线条。其他选项包括 LINE_4 和 LINE_AA。
shift:可选参数,表示坐标点的小数位数。默认值为 0,表示使用整数坐标。
作用介绍:
这个函数用于在图像上绘制标记、轨迹或线条等,用于可视化处理结果或进行目标跟踪等应用。
核心源代码:
static Point prev_pt2 = Point(-1, -1);
if (event == CV_EVENT_LBUTTONDOWN)
{
prev_pt2 = Point(x, y);
}
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
{
Point cur_pt2 = Point(x, y);
if (prev_pt2.x < 0)
{
prev_pt2 = cur_pt2;
}
line(org2, prev_pt2, cur_pt2, lineColor, tuyabi);
prev_pt2 = cur_pt2;
resize(org2, org2, Size(rect1.Width(), rect1.Height()));
imshow("img", org2);
}
else if (event == CV_EVENT_LBUTTONUP)
{
prev_pt2 = Point(-1, -1);
Matstack.push(org2);
setMouseCallback("img", NULL, &Matstack);
}
浮雕滤镜
首先定义定义浮雕滤镜卷积核;然后借助opencv提供的filter2D()函数进行滤镜处理;最后显示出调整后的图片。
filter2D() 函数的模板定义为:
void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor = Point(-1, -1), double delta = 0, int borderType = BORDER_DEFAULT);
参数说明:
src:表示输入图像的对象,通常是一个 cv::Mat 类型的变量。该参数是待卷积的图像。
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于存储卷积后的图像。
ddepth:表示输出图像的深度(数据类型)。通常使用 -1 表示与输入图像相同的深度。
kernel:表示卷积核的对象,通常是一个 cv::Mat 类型的变量。该参数是用于卷积操作的核。
anchor:可选参数,表示卷积核的锚点位置。默认值为 Point(-1, -1),表示将卷积核的中心作为锚点。
delta:可选参数,表示输出图像的偏移值。默认值为 0,表示不进行偏移。
borderType:可选参数,表示图像边界的处理方式。默认值为 BORDER_DEFAULT,表示使用默认的边界处理方式。
作用介绍:
这个函数用于图像处理中的滤波操作,通过卷积核对图像进行卷积运算,从而实现不同的图像处理效果,如边缘检测、模糊、锐化等。
核心源代码:
Mat originalImg = Matstack.top().clone();
Mat embossedImg;
Mat embossKernel = (Mat_<float>(3, 3) <<
-2, -1, 0,
-1, 1, 1,
0, 1, 2);
filter2D(originalImg, embossedImg, -1, embossKernel);
resize(embossedImg, embossedImg, Size(rect1.Width(), rect1.Height())); imshow("img", embossedImg);
Matstack.push(embossedImg);
模糊滤镜
首先定义滤波器的尺寸,用于计算均值;然后借助opencv提供的blur()函数进行处理;最后显示出处理后的图片。
blur() 函数的模板定义为:
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT);
参数说明:
src:表示输入图像的对象,通常是一个 cv::Mat 类型的变量。该参数是待滤波的图像。
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于存储滤波后的图像。
ksize:表示滤波核的大小,通常使用 cv::Size 类型的对象来表示。该参数指定了滤波核的宽度和高度。
anchor:可选参数,表示滤波核的锚点位置。默认值为 Point(-1, -1),表示将滤波核的中心作为锚点。
borderType:可选参数,表示图像边界的处理方式。默认值为 BORDER_DEFAULT,表示使用默认的边界处理方式。
作用介绍:
这个函数用于图像处理中的平滑操作,用于去除图像中的噪声、减少细节,并使图像更加模糊和平滑。
核心源代码:
Mat originalImg = Matstack.top().clone();
Mat filteredImg;
int kernelSize = 7;
blur(originalImg, filteredImg, Size(kernelSize, kernelSize));
Matstack.push(filteredImg);
resize(filteredImg, filteredImg, Size(rect1.Width(), rect1.Height()));
if (!roi.empty())
{
resize(filteredImg, filteredImg, Size(width, height));
filteredImg.copyTo(img(Rect(min(cur_pt.x, pre_pt.x), min(cur_pt.y, pre_pt.y), width, height)));
imshow("img", img);
}
else
{
Matstack.push(filteredImg);
resize(filteredImg, filteredImg, Size(rect1.Width(), rect1.Height())); imshow("img", filteredImg);
}
镜像功能
镜像功能借助opencv提供的flip()函数完成。
flip() 函数的模板定义为:
void flip(InputArray src, OutputArray dst, int flipCode);
参数说明:
src:表示输入图像的对象,通常是一个 cv::Mat 类型的变量。该参数是待翻转的图像。
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于存储翻转后的图像。
flipCode:表示翻转的方式,是一个整数值。常用的取值包括:
0:垂直翻转(沿 x 轴翻转)。
1:水平翻转(沿 y 轴翻转)。
-1:同时在水平和垂直方向翻转。
作用介绍:
这个函数用于图像处理中的镜像操作,用于生成图像的镜像版本,例如左右对称的图像展示、图像翻转等。
旋转功能
旋转功能借助opencv提供的rotate()函数完成。
rotate() 函数的模板定义为:
void rotate(InputArray src, OutputArray dst, int rotateCode);
参数说明:
src:表示输入图像的对象,通常是一个 cv::Mat 类型的变量。该参数是待旋转的图像。
dst:表示输出图像的对象,通常是一个 cv::Mat 类型的变量。该参数用于存储旋转后的图像。
rotateCode:表示旋转的方式,是一个整数值。常用的取值包括:
cv::ROTATE_90_CLOCKWISE:顺时针旋转图像90度。
cv::ROTATE_180:旋转图像180度。
cv::ROTATE_90_COUNTERCLOCKWISE:逆时针旋转图像90度。
作用介绍:
这段代码常用于图像处理中的旋转操作,用于调整图像的方向或角度。旋转可以是顺时针或逆时针的,并且可以指定旋转的角度。通过旋转操作,可以实现图像的纠正、角度调整等效果。
核心源代码:
Mat img2 = Matstack.top();
rotationAngle = (rotationAngle + 90) % 90;
Mat rotatedImg;
rotate(img2, img2, rotationAngle);
resize(img2, img2, Size(rect1.Width(), rect1.Height()));
imshow("img", img2);
Matstack.push(img2);
晕影功能
首先定义中心点的X坐标、中心点的Y坐标、中心点到图像边界的最大距离、晕影强度(范围为0.0到1.0);然后遍历图像的每个像素,根据距离中心点的远近,调整像素的亮度;最后显示出处理后的图片。
核心源代码:
Mat originalImg = Matstack.top().clone();
Mat filteredImg;
Mat vignetteImg;
double centerX = originalImg.cols / 2.0;
double centerY = originalImg.rows / 2.0;
double radius = sqrt(centerX * centerX + centerY * centerY);
double strength = 0.6;
for (int y = 0; y < originalImg.rows; ++y) {
for (int x = 0; x < originalImg.cols; ++x) {
double distance = sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY));
double factor = 1.0 - (distance / radius) * strength;
factor = std::max(0.0, factor);
Vec3b& pixel = originalImg.at<Vec3b>(y, x);
pixel[0] = static_cast<uchar>(pixel[0] * factor);
pixel[1] = static_cast<uchar>(pixel[1] * factor);
pixel[2] = static_cast<uchar>(pixel[2] * factor);
}
}
vignetteImg = originalImg;
Matstack.push(vignetteImg);
resize(vignetteImg, vignetteImg, Size(rect1.Width(), rect1.Height()));
imshow("img", vignetteImg);
拍照
首先打开摄像头,然后从摄像头读取一帧图像,最后显示图像在名为"img"的窗口中。
核心源代码:
cv::VideoCapture cap(0);
if (!cap.isOpened())
{
return;
}
cv::Mat frame;
cap >> frame;
resize(frame, frame, Size(rect1.Width(), rect1.Height()));
cv::imshow("img", frame);
cap.release();
二、在visual studio中配置opencv
请参考这篇文章
VS2022+OpenCV4.6.0+MFC环境配置_mfc 添加opencv-CSDN博客
或者参考B站视频
三、设计报告和项目源代码
如果你想获取完整设计报告及整个项目的源代码,请加V:dhmtdhkl或者Q:2898932764获取。
(注:该项目为武汉大学电子信息学院程序设计实践三的大作业之一)