# 图像处理工具 **Repository Path**: buptybx/image-processing-tools ## Basic Information - **Project Name**: 图像处理工具 - **Description**: 程序设计基础图像处理工具 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-07-31 - **Last Updated**: 2024-07-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 图片处理程序 ### 小组成员 孟广樾,戴立桓,朱佳雨,张怡 ### 项目介绍 - 项目介绍视频地址:[图像处理程序](https://www.bilibili.com/video/BV1pe411g7g4) - 本项目主要目标为完成一个简单的基于QT的对于.bmp 文件进行简单处理的图形化界面程序。 - 主要计划实现的内容为对于bmp文件进行打开与保存,以及进行缩放、裁剪、翻转、旋转、取反等基本操作,以及实现对于图片进行灰度化、均值滤波、中值滤波以及边缘识别等较为复杂的操作。 - 同时实现画笔功能,可以自由选择颜色以及画笔粗细在图片上进行自由绘制。 ### 成员分工 |成员 | 负责部分| |:---:|:---:| |孟广樾 |打开、保存、缩放、画笔| |戴立桓 |翻转、中值均值滤波、灰度、边缘识别| |朱佳雨 |图像取反、视频制作| |张怡 |裁剪、旋转、视频制作| - 项目主要利用git工具通过gitee平台进行远程协作。 ### 所用工具和资源 项目主要基于qt,利用qtcreater进行开发,主要利用QImage 、QLabel等qt提供的第三方库进行图片展示以及可视化窗口布局及接受用户的操作信号功能,除此之外,图片处理的代码全部为手动实现,未使用第三方接口。 --- ### 绪论 项目主要解决对bmp图片实现简单编辑操作的可视化,及时反馈的图形操作界面。利用面向对象的方法,基于面向对象程序设计课程中,老师提供的对于bmp文件操作的指导文档中指出的各种方法,实现的对于图片操作的各种函数。将这些方法重写转移至qt平台上实现图形化操作界面。 ## 各方法介绍 ### 图片的打开与保存 1. 图片的打开操作 在获得用户点击打开的信号之后,通过qt提供的getOpenFileName方法获取用户需要打开的图片的路径地址。 而后调用Image类中ReadBMP方法对mainwindow类成员指针img进行初始化操作,同时利用拷贝构造函数进行深拷贝初始化origin_img指针数组,以便于实现重做等功能。 而后利用重载的类型转换函数实现自定义的image类与QImage类的转换,并将其显示在label组件上。 **伪代码**: ``` 获得用户点击信号{ filename = 获取用户选择图片地址; 若地址不为空{ 利用filename初始化img指针; 将img指针拷贝给origin_img指针数组的每一个成员; 调用showimage方法; //该方法将img成员转换为可被展示的QImage类,并将其展示出来 } } ``` 2. 图片保存 类似于图片的打开操作,获取地址后调用WriteBMP方法即可,无需进行图片展示。 --- ### 图片缩放 在获得用户的缩放信号之后,弹出对话框,让用户自行填写所需缩放的大小,而后调用Image类中的Resize函数对图片进行缩放,Resize函数根据缩放前后的图片比例,获取院原图片对应位置的像素点至新的图片中实现图片缩放,而后调用ShowImage函数进行展示。 **伪代码** ``` 获得用户点击信号{ 新建对话框,令用户填写所需缩放的大小; 如果用户填写了结果{ 获取对话框中用户填写的数字; 调用img->Resize方法,传入获得的数值,实现缩放; 调用ShowImage方法展示图片; } } ``` --- ### 图片旋转 根据用户所需进行不同方向的旋转,调用Rotate函数,顺时针方向则传入90,逆时针则传入-90,函数中将存储图片数据的数组进行简单的旋转操作。 **伪代码** ``` 用户点击顺时针{ 调用img->Rotate(90); 展示img; }; 用户点击逆时针{ 调用img->Rotate(-90); 展示img; }; ``` --- ### 图片裁剪 在获得用户点击的信号后,弹出对话框,用户填写所需要裁剪的坐标的起始点,在获得坐标后,调用Cut()函数保留图片对应位置的数据。 **伪代码** ``` 获得用户点击信号{ 弹出裁剪坐标填写的对话框; 若用户填写了坐标{ 调用img->Cut函数,传入获得的坐标; 调用ShowImage展示图片; } } ``` --- ### 取反 将图像的像素取反,即黑的变成白的,白的变成黑的。原像素值的范围是在【0,255】,首先需要将每个像素值都利用normalize函数等比缩小到【0,1】区间内,然后取反即是每个像素都被1减。 其中normalize具体实现公式为t'=(t-min)/max。 **伪代码** ```c++ "Normalize()" for i:0 TO height*width*3 { if max < data[i] Then{ max = data[i]; } if min > data[i] Then{ min = data[i]; } } if max != 0 Then { for i:0 TO height*width*3 { double num; num = data[i]; if num == min Then{ data[i] = 0; } else if num == max Then{ data[i] = 1; } else { data[i] = (num - min) / max; } } } ``` ```c++ "Operator-()" double min = data[0], max = data[0]; int i; for i:0 TO height*width*3 { if min > data[i] Then{ min = data[i]; } if max < data[i] Then{ max = data[i]; } } Normalize(); for i:0 TO height*width*3 { data[i] = 1 - data[i]; data[i] = data[i] * max + min; } return (*this); ``` --- ### 图像翻转 获得用户信号后调用flip函数进行翻转操作。这完全可以当成矩阵处理,然后就是对称点互换位置,难点就是一维数据处理,i行j列t位应这样表示(i*width+j)*3+t,其他的位置也可以推出。 **伪代码** ``` 传入code值; 如果code值输入为0则为左右翻转{ i从0开始到图片的高度{ j从0开始到图片的一半宽度{ t从0开始到3 { 交换data的i行j列的t位与data的i行(宽度-j-1)列的t位 } } } }//原代码是一维的,t位对应bgr值 如果code值输入为0则为上下翻转{ i从0开始到图片的一半高度{ j从0开始到图片的宽度{ t从0开始到3 { 交换data的i行j列的t位与data的(高度-i-1)行j列的t位; } } } } ``` --- ### 中值滤波 实现方法为取filtersize为边长的矩形滤波器,依次将每个点用滤波器扫描,将范围内点的值利用sort函数排序后取中值并赋值给中心点。 中值滤波和均值滤波都继承了同一个类filter ``` filter : Public: 构造函数(输入size,将size赋值给filtersize) 析构函数 以及一个作为纯虚函数的_滤波函数_ Protected: _filtersize_ ``` **伪代码** ``` 读取input的高度和宽度 创建一个同宽高的对象output 建立一个一维数组range判断是否处在边缘 t从0到3{ i从0到图像高度{ j从0到图片宽度{ range[0]储存i行j列中心点的上边缘,如果越界<0则取0否则不做处理,其余三个对应剩下三个边缘 然后将划定区域范围内的数据读入到一个数组中用sort进行排序 将数组中间位的数据赋值给output的i行j列t位的数据}}} 返回output ``` --- ### 均值滤波 均值滤波与中值滤波大同小异,将取矩形框中所有数据的中值操作替换为取平均值操作。 ### 灰度 灰度图公式:b,g,r=b值*0.114+g值*0.5870+r值*0.299。 **伪代码** ``` i从0开始到图像高度{ j从0到图像宽度*3{ 将i行j/3列的r,g,b带入公式赋值给变量average average再赋值给对应点的r,g,b值}} ``` --- ### 边缘检测 **伪代码** ``` 首先将图像灰度化 对图像进行5*5的高斯模糊(高斯模糊等同于均值滤波) //sobel算子滤波 用两个算子Gx=[-1 0 1] Gy=[-1 2 -1]对进行竖直方向滤波与水平方向滤波 [-2 0 2] [ 0 0 0] [-1 0 0] { 1 2 1] 将两次分别滤波后的值a和b带入公式temp = sqrt(a * a + b * b),几何平均求出梯度大小; 将temp值赋值给对应的数据点 ``` --- ### 画笔 画笔功能基于参考资料,自定义了QLabel的派生类MyLabel,重写了有关鼠标移动的函数,以实现监测鼠标事件,及时反馈鼠标的坐标,并做出相应的动作。 记录每一次鼠标移动的坐标点,利用STL库存储坐标,并自己实现函数绘制存储的相邻两坐标之间的直线,同时,对于每一个点绘制半径可选的圆形,以实现调整画笔粗细的功能,利用getColor 方法获取用户选择的rgb来自定义画笔的颜色。 **伪代码** ``` mainwidow.cpp: 用户点击画笔选项{ 将paint类的isPaint属性设置为true,同时传入img成员; }; 用户点击颜色选项{ 弹出对话框选择颜色; 如果用户选择了颜色{ 调用paint->SetRGB函数,传入对应函数值; } }; MyLabel.cpp: 如果isPaint属性为true: 若检测到鼠标点击{ 调用paint->MousePress函数; }; 若检测到鼠标移动{ 调用paint->MouseMove函数; 调用paint->PaintLine函数绘制直线; 调用img->ShowImage函数展示图片; }; 若检测到鼠标释放{ 调用paint->MouseRelease函数; } Painter.cpp: MousePress{ 新增直线; 新增点; }; MouseMove{ 新增点; } MouseRelease{ 新增点; } PaintLine{ 遍历STL容器内所有直线的所有点{ 每两点之间绘制所有在两点连成的直线上的点绘制半径为r的圆; } } ``` --- ## 总结与讨论 经过本次实验,我发现图像进行处理变换并没有想象中那么难,主要因为已经有前人总结好公式与方法,需要做的就是将公式与方法与qt进行良好的结合。一开始其实是并不清楚该怎么把自己的代码和设计的按钮链接起来,这也是这个项目初期的难点,通过自学qt以及通过与队友互相交流借鉴才得以完成任务 。较为麻烦的还有将三通道数据转变为一维数据处理,需要多次代换验证保证准确。 关于qt的界面设计,虽说可以利用代码实现,但是最后还是决定利用qt的界面编辑器,因为界面编辑器比代码实现更美观,简洁,快速,这界面编辑器是极其容易上手。 在本次项目中,虽然实现的功能较为简单,但是可以对简单的图像处理程序有初步的认识,实现了一张图片从打开,再根据需求进行裁剪或者旋转等操作,最后进行显示这整个流程的实现。 ## 成员贡献比例 | 成员 | 贡献占比 | |:--:|:--:| |孟广樾| 30% | |戴立桓|26%| |朱佳雨|24%| |张怡|20%| ## 引用参考 1. [边缘检测算法的C++实现](https://www.cnblogs.com/thhyj/p/12054705.html) 2. [QT实现画笔功能](https://blog.csdn.net/geduo_feng/article/details/102999980)