学生姓名:
学 号:
学 院: 信息学部 专 业: 计算机技术
2016年11月
前言
本论文的编写围绕四个项目:图像空域/时域变换、图像增强、染色体计数与提取Mnist链码组成。项目的编写基于Windows 7 操作系统,使用VS2013作为开发环境,以OpenCV作为内部核心处理算法库。项目的最终组成结构为:
在此,我们不再说明OpenCV的配置环境的搭建,而是直接展示项目的核心代码以及所使用到的知识。
题目一
1) 将宽为2n的正方形图像,用FFT算法从空域变换到频域,并用频域图像的模来进行显
示。
2) 使图像能量中心,对应到几何中心,并用频域图像的模来进行显示。 3) 将频域图象,通过FFT逆变换到空域,并显示。 解决方案:
我们在一个项目中完成以上三个步骤。
#include //以灰度模式读取原始图像并显示 Mat srcImage = imread(\"lena.png\", 0); if (srcImage.empty()) { cout << \"打开图像失败!\" << endl; return -1; } imshow(\"原始图像\", srcImage); //将输入图像延扩到最佳的尺寸,边界用0补充 int m = getOptimalDFTSize(srcImage.rows); int n = getOptimalDFTSize(srcImage.cols); //将添加的像素初始化为0. Mat padded; copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0)); //为傅立叶变换的结果(实部和虚部)分配存储空间。 //将planes数组组合合并成一个多通道的数组complexI Mat planes[] = { Mat_ CV_32F) }; Mat complexI; merge(planes, 2, complexI); //进行就地离散傅里叶变换 dft(complexI, complexI); //将复数转换为幅值,即=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2)) split(complexI, planes); // 将多通道数组complexI分离成几个单通道数组,planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude Mat magnitudeImage = planes[0]; //进行对数尺度(logarithmic scale)缩放 magnitudeImage += Scalar::all(1); log(magnitudeImage, magnitudeImage);//求自然对数 //剪切和重分布幅度图象限 //若有奇数行或奇数列,进行频谱裁剪 magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2)); //重新排列傅立叶图像中的象限,使得原点位于图像中心 int cx = magnitudeImage.cols / 2; int cy = magnitudeImage.rows / 2; Mat q0(magnitudeImage, Rect(0, 0, cx, cy)); // ROI区域的左上 Mat q1(magnitudeImage, Rect(cx, 0, cx, cy)); // ROI区域的右上 Mat q2(magnitudeImage, Rect(0, cy, cx, cy)); // ROI区域的左下 Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); // ROI区域的右下 //交换象限(左上与右下进行交换) Mat tmp; q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); //交换象限(右上与左下进行交换) q1.copyTo(tmp); q2.copyTo(q1); tmp.copyTo(q2); //归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式 normalize(magnitudeImage, magnitudeImage, 0, 1, CV_MINMAX); //显示效果图 imshow(\"频域\", magnitudeImage); //(3)频域-->空域 Mat inversed; dft(complexI, inversed, DFT_INVERSE | DFT_REAL_OUTPUT); normalize(inversed, inversed, 0, 1, CV_MINMAX); imshow(\"空域\", inversed); waitKey(); return 0; } 效果图: 题目二 对于下面这幅图像,请问可以通过那些图像增强的手段,达到改善视觉效果的目的?请显示处理结果,并附简要处理流程说明。 该图像存在的主要问题: 1. 存在椒盐噪声 2. 曝光不够导致光线昏暗 解决方案: 1. 采用中值滤波去噪 2. 增加亮度,调整对比度 详细代码: #include int ContrastValue; //对比度值 int BrightValue; //亮度值 Mat src, dst; //改变图像对比度和亮度值的回调函数 static void ContrastAndBright(int, void *) { //创建窗口 namedWindow(\"【原始图窗口】\", WINDOW_AUTOSIZE); //更改图像亮度与对比度 for (int y = 0; y < src.rows; y++) { for (int x = 0; x < src.cols; x++) { for (int c = 0; c < 3; c++) { dst.at saturate_cast 0.01)*(src.at } } } //显示图像 imshow(\"【原始图窗口】\", src); imshow(\"【效果图窗口】\", dst); } int main(int argc, char *argv[]) { //打开图像 src = imread(\"two.png\"); if (src.empty()) { cout << \"打开图像失败!\" << endl; return -1; } //中值滤波去噪 medianBlur(src, src, Size(5, 5)); dst = Mat::zeros(src.size(), src.type()); //设定对比度和亮度的初值 ContrastValue = 80; BrightValue = 80; //创建窗口 namedWindow(\"【效果图窗口】\", WINDOW_AUTOSIZE); //创建轨迹条 createTrackbar(\"对比度:\", \"【效果图窗口】\", &ContrastValue, 300, ContrastAndBright); createTrackbar(\"亮 度:\", \"【效果图窗口】\", &BrightValue, 200, ContrastAndBright); //调用回调函数 ContrastAndBright(ContrastValue, 0); ContrastAndBright(BrightValue, 0); //等待用户按键,起到暂停的作用 waitKey(); return 0; } 题目三 对于下面这幅图像,编程实现染色体计数,并附简要处理流程说明。 解决方案: 1. 图像存在椒盐噪声,所以先利用中值滤波对图像进行去噪处理。 2. 进行二值化。 3. 因为图像中染色体内、染色体间存在空白点,所以要进行腐蚀操作。 4. 接下来就是要求以上图像存在的染色体的个数了。抛开数字图像处理的专业性知识, 利用我们以往掌握的知识,该怎么去做呢? 在数据结构中,我们学到过连通区域、深度优先遍历等知识。那么,我们是不是把上图看做是由一个个染色体组成的连通区域呢?没错,求染色体的个数转化为求连通区域(黑色区域)的个数。一次DFS(深度优先遍历)便可以求得一个连通区域,也就是求得存在一个染色体。我们都知道DFS最简单的实现方式是使用递归函数。递归调用是个很美的函数,但是递归函数调用也会带来开栈、清栈性能上的损失。虽然以上算法性能上不是最优的,但是也不失为一种解决办法。 以下是利用数字图像处理里的相关知识:利用OpenCV里的findContours函数检测染色体边缘,然后统计染色体个数。 根据我们观察,以上图像含有46个染色体,但是我们求出来的是47条。根据我的个人理解:整个图像的边缘构成了一个最大的轮廓线,所以正确的染色体个数应为46,而不是47。=.=口说无凭,还是拿代码来说话。我们可以把轮廓的坐标打印出来: for (int i = 0; i < contours.size(); i++) { for (int j = 0; j < contours[i].size(); j++) { cout << contours[i][j] << \" \"; } cout << \";\" << endl; } 打印出:[1, 1] [1, 410] [523, 410] [523, 1] 。而我们的染色体这幅图片的大小为:525 * 412。虽然略有偏差,但是足可以证明以上假设。 详细代码: #include int main(int argc, char **argv) { Mat gray, src, dst; //打开图像 src = imread(\"image.png\"); if (src.empty()) { cout << \"打开图像失败!\" << endl; return -1; } cout << \"rows = \" << src.rows << endl; cout << \"cols = \" << src.cols << endl; //转换为灰度图 cvtColor(src, gray, CV_BGR2GRAY); //中值滤波 medianBlur(gray, gray, 7); //图像二值化 threshold(gray, dst, 170, 255, THRESH_BINARY); //腐蚀,默认内核3*3 erode(dst, dst, Mat()); //erode(dst, dst, Mat()); Mat canny_output; vector //画轮廓线 Canny(dst, canny_output, 100, 100 * 2, 3); //检测轮廓 findContours(dst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); cout << \"一共检测到染色体数目:\" << contours.size() - 1 << endl; //显示图片 imshow(\"src\", src); imshow(\"canny_output\", canny_output); //将图片保存到文件 imwrite(\"dst.png\", canny_output); //等待用户输入 waitKey(); return 0; } 题目四 对MNIST手写数字数据库(可在网上搜索下载),编程实现来提取其链码。 解决方案: 1. 将Mnist字库读取到以OpenCV里的Mat为储存单元的vector中。 2. 使用findContours将链码保存在vector中。 3. 输出链码。 #include int ReverseInt(int i) { unsigned char ch1, ch2, ch3, ch4; ch1 = i & 255; ch2 = (i >> 8) & 255; ch3 = (i >> 16) & 255; ch4 = (i >> 24) & 255; return((int)ch1 << 24) + ((int)ch2 << 16) + ((int)ch3 << 8) + ch4; } /** * 将Mnist数据库读取到OpenCV::Mat格式中 * 格式: * magic number * number of images * rows * cols * a very very long vector contains all digits */ void read_Mnist(string filename, vector ifstream file(filename, ios::binary); if (file.is_open()) { int magic_number = 0; int number_of_images = 0; int n_rows = 0; int n_cols = 0; file.read((char*)&magic_number, sizeof(magic_number)); magic_number = ReverseInt(magic_number); file.read((char*)&number_of_images, sizeof(number_of_images)); number_of_images = ReverseInt(number_of_images); file.read((char*)&n_rows, sizeof(n_rows)); n_rows = ReverseInt(n_rows); file.read((char*)&n_cols, sizeof(n_cols)); n_cols = ReverseInt(n_cols); for (int i = 0; i < number_of_images; ++i) { cv::Mat tp = Mat::zeros(n_rows, n_cols, CV_8UC1); for (int r = 0; r < n_rows; ++r) { for (int c = 0; c < n_cols; ++c) { unsigned char temp = 0; file.read((char*)&temp, sizeof(temp)); tp.at vec.push_back(tp); } }//if } int main(int argc, char **argv) { int count = 1; //存储Mnist字库 vector //将Mnist字库读取到vector中 read_Mnist(\"t10k-images.idx3-ubyte\", vec); cout << \"共含有:\" << vec.size() << \"幅图片\" << endl; for (auto iter = vec.begin(); iter != vec.end(); iter++) { cout << \"第\" << count++ << \"幅图片...\" << endl; //显示Mnist字库 imshow(\"Mnist\", *iter); vector findContours(*iter, contours, CV_RETR_EXTERNAL, CV_CHAIN_CODE); //输出链码 for (int i = 0; i < contours.size(); i++) { for (int j = 0; j < contours[i].size(); j++) { cout << contours[i][j]; } cout << endl; } waitKey(1000); } waitKey(); return 0; } 效果图: 从以上输出看到,我们所得到的链码并不是我们所熟悉的4方向和8方向的链码。无论是在StackOverFlow还是知乎、Google均没有找到相关资料。不得不说是一个遗憾。 总结 以下是自己完成本次论文所学习和使用到的知识点: 图像噪声:就像对于听觉而言,在打电话时对方说话我们有时候会听到很嘈杂的噪声,以至于听不清楚对方在说什么。同样的,对于图像,原本我们可以很清晰的看到一幅图像,但是有时候图像上会有一些我们不需要的图案,使我们无法很清楚的看清一幅图,这就是图像的噪声。 常见的图像噪声的分类: 1. 高斯噪声 高斯噪声是一种具有正态分布(也称作高斯分布)概率密度函数的噪声。换句话说,高斯噪声的值遵循高斯分布或者它在各个频率分量上的能量具有高斯分布。它被极其普遍地应用为用以产生加成性高斯白噪声(AWGN)的迭代白噪声。 2. 瑞利噪声 3. 伽马噪声 4. 指数分布噪声 5. 均匀分布噪声 6. 椒盐噪声 椒盐噪声也称为脉冲噪声,是图像中经常见到的一种噪声,它是一种随机出现的白点或者黑点,可能是亮的区域有黑色像素或是在暗的区域有白色像素(或是两者皆有)。 常用的去除这种噪声的有效手段是使用中值滤波器。 例如:带有椒盐噪声的图像: 原始图像: 直方图:直方图是对数据的集合统计,并将统计结果分布于一系列预定义的 bins 中。 这里的数据不仅仅指的是灰度值 ,统计数据可能是任何能有效描述图像的特征。 形态学操作:形态学操作就是基于形状的一系列图像处理操作。通过将 结构元素 作用于输入图像来产生输出图像。最基本的形态学操作有:腐蚀与膨胀(Erosion 与 Dilation)。 二值图像:二值图像是每个像素只有两个可能值的数字图像。人们经常用黑白、B&W、单色图像表示二值图像,但是也可以用来表示每个像素只有一个采样值的任何图像,例如灰度图像等。 OpenCV(Open Source Computer Vision Library):OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。 Mnist:MNIST是一个标准的手写字符测试集,收集者是人工智能领域著名的科学家,现在最火的深度学习网络Convulution Nueral Networks的创始人,现任Facebook AI实验室的主任 -- Yann LeCun。 因篇幅问题不能全部显示,请点此查看更多更全内容