数字图像处理小作业1
最近软件工程课要我们用博客记录项目。我感觉这样做挺好的,一来可以让项目的开发更规范化,二来可以给博客和github添加一些内容,不让大学生活看起来很空虚。以后我所有项目作业尽量都建立仓库和博客。
代码仓库
https://github.com/SingleZombie/RGB-CMY-HSI-transformer-homework
需求分析
- 原图像一张RGB(彩色图像)
- 分别在RGB,CMY,和HSI三个空间通过调整颜色实现一种风格化转换(风格自选)
- 需要提交源码以及不超过半页的代码说明(说明使用的库和参数)
- 压缩包内文件命名方式为:
-图像命名:
0(此原图像)
1RGB,2CMY,3HSI(此为处理结果)
-文档命名:学号_姓名_1
-代码包命名:学号_姓名_1
以上是老师给的作业要求。据说老师上课的时候还提到,三种通道的风格化结果要看起来相等。
我对风格化的理解是,改变某种彩色表示下某个分量后得到的结果。
经总结,需求有以下内容:
- 实现RGB到CMY和HSI的转换
- 在某种表示下改变图片风格,并调整另两种表示下的结果使得图片看起来相同
- 写说明文档
技术学习
OpenCV入门
OpenCV安装与配置
在本作业中,我打算用OpenCV来读、写、显示图像文件。在一些任务开始前,我要先学OpenCV的用法。
我是在Win10下用VS2017编程,生成的是x86程序。
- 去 https://opencv.org/releases/ 下载Windows版本的库文件。下载后能得到一个exe文件,把该文件“解压”到一个路径即可。解压的文件夹里有一个opencv子文件夹。记此子文件夹为
$(OPENCV)
。 x86的OpenCV库编译(可选)
- 去https://opencv.org/releases/ 下载Sources,这样编译出来的库可以满足当前编译器的配置。(直接安装的话没有x86的文件)
- 用cmake配置源代码。注意源代码的路径最好不要包含中文。(我第一次包含了中文,结果vs编译到一半出错了)
- 用vs打开cmake生成的目录下的OpenCV.sln,编译源代码。
- 编译结束后在目录的lib文件夹中找到编译的结果文件夹(Debug或Release),里面所有.lib文件就是编译的结果,把它们放到上一步的build文件夹里。我按照格式放到了
$(OPENCV)\build\x86\vc15\lib
文件夹里。 - 编译出的bin文件夹需要加入系统环境变量,因为里面的dll会被用到。改完环境变量重启电脑。
- 在VS中的属性管理器新建配置,添加用户宏OPENCV,宏值就是开始那个解压出的opencv子文件夹。
$(OPENCV)
现在就表示库目录了。 - 在属性的包含目录里加上
$(OPENCV)\build\include
,库目录改成对应版本的库的目录,比如我的就是$(OPENCV)\build\x86\vc15\lib
。 - VS链接器-输入-附加依赖项中加入需要的.lib文件。我的第一个程序加入了opencv_highgui420d.lib;opencv_core420d.lib;opencv_imgcodecs420d.lib这三个库
- 我随便到网上找了下函数,依葫芦画瓢地写了以下代码。如果一切都配置好了,程序会显示main.cpp目录下wallpaper.jpg这张图片。
1 |
|
OpenCV 像素获取与修改
OpenCV的操作很多都基于Mat
的对象,也就是图像矩阵。
1 | unsigned char R, G, B; |
对矩阵的at操作可以get或set颜色分量的值。
注意RGB是反的。
OpenCV输入输出图像
1 | cv::Mat mat = cv::imread("in.png"); |
输入输出都是基于Mat
的对象。
RGB转CMY、HSI
RGB转CMY
看了一下,RGB符合人对颜色的认知。但现实中,为了方便打印出黑色,用CMY表示颜料的颜色更加方便。
CMY就是RGB取反,即:
1 | C = 255 - R; |
RGB转HSI
我对HSI的大致理解是:H是颜色的属性,比如是红色、绿色、蓝色还是其他颜色;S是颜色被稀释的属性,也就是颜色的深浅;I是光强,是颜色向量的模的大小。
https://blog.csdn.net/yangleo1987/article/details/53171623 里有好几个RGB转HSI方法的介绍。其中第一种是上课提到的方法。
风格转换
我问了一下别人,又看了一下书。感觉风格转换就是对某个颜色通道做一个函数映射,使得图片整体风格改变。
结构设计
由于程序过于简单,该程序用结构化的设计方法。
输入经过输入预处理模块,进入处理模块,最后进入输出模块输出。整个结构非常简明而无趣。
1 | // ****************** *********************** ******************* |
由于要提交源代码文件,我把代码全部放到一个main.cpp里面了。但在我心中,还是为每个模块各建了一个头文件和cpp文件的。
程序设计
程序概览
代码放在Github上:代码仓库
1 | int main() |
main函数非常简明,getInput
和outputImage
隐藏了输入输出细节,方便修改。事实上,图像读入时像素是用unsigned char存储的,我在输入的时候把它转换成了float,输出的时候又转了回去。
1 | for (int i = 0; i < mat.rows; i++) |
processImage
的主体是遍历像素,先把原图像做格式转换,再用自己的风格转换函数瞎搞,最后转换回正常的格式。
1 | inline cv::Vec3f bgrToYmc(const cv::Vec3f& vec) |
格式转换函数完全是在照搬公式而已,没有什么价值。
写程序过程中碰到的问题
- 为了公式处理方便,我把像素格式转换成了float。我一开始以为直接就能通过
mat.at<Vec3f>
来访问浮点格式像素,但是发现cv::Mat
的机制好像是只能允许像素按一种格式存。要把图像矩阵用.convertTo(mat, CV_32FC3, 1 / 255.0)
来转换成浮点表示,最后输出的时候还要用.convertTo(tmpMat, CV_8UC3, 255.0f)
转换回去。 - 我还好提前看了一下
cv::Mat
的工作原理。该类只存了图像数据的指针,要复制图像数据的话要调用src.copyTo(dest)
- 图片转HSI再转回来后,我发现图像中出现一些绿色方块。经调试发现,H分量在运算的时候变成了nan。计算H的时候一定要判断分母是否为0!
- 图像的RGB是倒着存的。我之前测试OpenCV的时候碰到了这个问题,写这个程序的时候就跳过了这个坑。
处理结果
原图
RGB风格转换结果
CMY风格转换结果
HSI风格转换结果
感想
写博客真浪费时间。