在本周的计算机视觉与模式识别作业中,给定输入图像是两张普通A4打印纸,上面可能有手写笔记或者打印内容但是拍照时角度不正。要求输出:
1. 图像的边缘;
2. 计算 A4纸边缘的各直线方程;
3. 提取A4纸的4个角点。
作业要求的是使用C++的CImg库,与OpenCV和MATLAB相比,CImg还是有点不太方便。
- 图像边缘的提取
这个任务比较简单,找一个像Prewitt算子、拉普拉斯算子或是其他算子,对输入图像进行卷积计算就可以了,原理是计算图像像素的变化,而变化剧烈的地方就是图像边缘出现的地方。
这里我使用了Sobel算子来进行边缘提取并二值化。
int sobelX[3][3] = { { -1,0,1 },{ -2,0,2 },{ -1,0,1 } };
int sobelY[3][3] = { { 1,2,1 },{ 0,0,0 },{ -1,-2,-1 } };
Sobel算子就是分别求图像X、Y方向的梯度,再求梯度平方和,小于某个阈值置为0,反之为255。
最后结果如下:
- Harris角点的检测
主要时间都花费在这部分的实现上面了。这里推荐一篇文章,里面详解了Harris角点检测算法的原理、步骤,还给出了OpenCV实现的代码。
http://www.cnblogs.com/ronny/p/4009425.html
还有一些有帮助的文章:
http://blog.csdn.net/l_inyi/article/details/8915116
http://blog.csdn.net/berguiliu/article/details/24985825
关键步骤如下:
而我自己则是在理解该文章所写算法的基础上,使用CImg库按步骤地搜索网上的资料,完成每一部分之后,检测出了角点。
在实现过程中遇到的问题主要有:
- 在求x、y方向梯度的时候,一开始使用的是一位的模板,导致检测到的角点过多,容易混淆。后来经过实验,改成了二维的3X3模板,效果较好;
- 在第三步使用高斯加权的时候,我以为是对各梯度矩阵使用高斯分布的公式:
角点检测的结果:
明显不太对。
后来查阅资料才认识到应该使用高斯低通滤波窗口对Ixx、Iyy、Ixy进行卷积。更正之后的结果:
- 求边缘直线方程
在Harris角点检测所得的图片当中,可以看到四个角的角点是独立出来的,能够比较容易地提取它们的坐标。因此可以利用Harris角点检测过程中计算的result数组来找到四个顶点的坐标,有了坐标之后四条直线的方程也就知道了。
最后求得的方程:
差点忘了贴代码:
// CV_HW2.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <algorithm>
#include "CImg.h"
using namespace cimg_library;
using namespace std;
/*
CImg<> edgeDetect(CImg<> src) {
int mask[3][3] = { {0,-1,0},{-1,4,-1},{0,-1,0} };
int width = src.width();
int height = src.height();
CImg<double> newImg(width, height, 1, 3);
newImg.fill(0);
for (int i = 1; i < width - 1; i++) {
for (int j = 1; j < height - 1; j++) {
for (int k = 0; k < 3; k++) {
double sum = 0.0;
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
sum += mask[row][col] * src(i - 1 + row, j - 1 + col, k);
}
}
if (sum < 0)
sum = 0;
else if (sum > 255)
sum = 255;
newImg(i, j, k) = sum;
}
}
}
newImg.display();
return newImg;
}
*/
CImg<> edgeDetect(CImg<> src) {
int sobelX[3][3] = { { -1,0,1 },{ -2,0,2 },{ -1,0,1 } };
int sobelY[3][3] = { { 1,2,1 },{ 0,0,0 },{ -1,-2,-1 } };
int width = src.width();
int height = src.height();
CImg<double> G(width, height, 1, 3);
CImg<double> newImg(width, height, 1, 3);
newImg.fill(0);