计算机视觉实验报告
Ex2:用 CImg 重写、封装给定的 Canny 代码,并测试
- 附件有三个 Canny 相关的 Code 以及测试数据若干(测试数据自己转化成 BMP 图
像); - 同学按照各自学号最末尾的数字除 3 取余数,余数为 0 的改写 Code0,余数为 1
的改写 Code1,余数为 2 的改写 Code2; - 封装要求: (1)所有的图像读写、数据处理只能用 CImg 库(整个工程文件不允许
使用 Opencv 之类的第三方库); (2)代码封装要求函数接口简洁清晰, 可参考
Code2 的方式封装。 - 在原来的代码基础上, 增加一个函数: 首先把相邻的边缘连成长的线条,并删除
长度小于 20 的 Edge。 分析删除这些短 Edge 有何优缺点? - 对算法的若干组参数,对所有测试图像进行测试, 保存输出 Canny 算法每个主
要步骤的结果图像, 并分析各参数对结果的影响。
这里我们要改写的代码为code1
具体做法:
首先我们先查看一下code1中的代码,在代码中我们首先找到了关于canny边缘检测的几个博客,首先这里我们先说一下实验原理:
Canny-Edge-Algorithm:
实现 canny 边缘检测的原理通俗来说就是用离散化的梯度逼近函数根据二维灰度矩阵梯度向量来寻找图像灰度矩阵的灰度跃变位置,然后再图像中将这些点连起来就形成了图像的边缘。
开始
首先我们写的程序针对的图像类型是bmp,这里我直接没有打码,而是直接将图片通过转换工具转换成bmp24位位图格式。
接下来我们需要了解以下几个过程
1、图像的灰度化:
Gimg = 0.299R+0.587G+0.114B
话不多说,上代码。
void canny::toGrayScale()
{
grayscaled.assign(img._width, img._height); //To one channel
cimg_forXY(img, x, y) {
int r = img(x,y,0);
int g = img(x,y,1);
int b = img(x,y,2);
double newValue = (r * 0.2126 + g * 0.7152 + b * 0.0722);
grayscaled(x,y) = (unsigned char)(newValue);
}
}
这里使用cimg头文件编写灰度化代码类toGrayScale()。
测试灰度,效果如下:
使用sobel算子计算各点梯度矢量。
这里有对sobel算子的主要介绍:sobel算子
void canny::sobel()
{
//Sobel X Filter
double x1[] = {-1.0, 0, 1.0};
double x2[] = {-2.0, 0, 2.0};
double x3[] = {-1.0, 0, 1.0};
vector< vector<double> > xFilter(3);
xFilter[0].assign(x1, x1+3);
xFilter[1].assign(x2, x2+3);
xFilter[2].assign(x3, x3+3);
//Sobel Y Filter
double y1[] = {1.0, 2.0, 1.0};
double y2[] = {0, 0, 0};
double y3[] = {-1.0, -2.0, -1.0};
vector< vector<double> > yFilter(3);
yFilter[0].assign(y1, y1+3);
yFilter[1].assign(y2, y2+3);
yFilter[2].assign(y3, y3+3);
//Limit Size
int size = (int)xFilter.size()/2;
sFiltered = CImg<unsigned char>(gFiltered._width - 2*size, gFiltered._height - 2*size);
angles = CImg<unsigned char>(gFiltered._width - 2*size, gFiltered._height - 2*size); //AngleMap
for (int i = size; i < gFiltered._height - size; i++)
{
for (int j = size; j < gFiltered._width - size; j++)
{
double sumx = 0;
double sumy = 0;
for (int x = 0; x < xFilter.size(); x++)
for (int y = 0; y < xFilter.size(); y++)
{
sumx += xFilter[x][y] * (double)(gFiltered(j + y - size, i + x - size)); //Sobel_X Filter Value
sumy += yFilter[x][y] * (double)(gFiltered(j + y - size, i + x - size)); //Sobel_Y Filter Value
}
double sumxsq = sumx*sumx;
double sumysq = sumy*sumy;
double sq2 = sqrt(sumxsq + sumysq);
if(sq2 > 255) //Unsigned Char Fix
sq2 =255;
sFiltered(j-size, i-size) = sq2;
if(sumx==0) //Arctan Fix
angles(j-size, i-size) = 90;
else
angles(j-size, i-size) = atan(sumy/sumx);
}
}
}
其次对梯度幅值进行最大限制:
void canny::nonMaxSupp()
{
nonMaxSupped = CImg<unsigned char>(sFiltered._width-2, sFiltered._height-2);
for (int i=1; i< sFiltered._width - 1; i++) {
for (int j=1; j<sFiltered._height - 1; j++) {
float Tangent = angles(i,j) * 57.296f;
// cout << Tangent << ' ';
nonMaxSupped(i-1, j-1) = sFiltered(i,j);
//Horizontal Edge
if (((-22.5 < Tangent) && (Tangent <= 22.5)) || ((157.5 < Tangent) && (Tangent <= -157.5)))
{
if ((sFiltered(i,j) < sFiltered(i+1,j)) || (sFiltered(i,j) < sFiltered(i-1,j)))
nonMaxSupped(i-1, j-1) = 0;
}
//Vertical Edge
if (((-112.5 < Tangent) && (Tangent <= -67.5)) || ((67.5 < Tangent) && (Tangent <= 112.5)))
{
if ((sFiltered(i,j) < sFiltered(i,j+1)) || (sFiltered(i,j) < sFiltered(i,j-1)))
nonMaxSupped(i-1, j-1) = 0;
}
//-45 Degree Edge
if (((-67.5 < Tangent) && (Tangent <= -22.5)) || ((112.5 < Tangent) && (Tangent <= 157.5)))
{
if ((sFiltered(i,j) < sFiltered(i+1,j+1)) || (sFiltered(i,j) < sFiltered(i-1,j-1)))
nonMaxSupped(i-1, j-1) = 0;
}
//45 Degree Edge
if (((-157.5 < Tangent) && (Tangent <= -112.5)) || ((22.5 < Tangent) && (Tangent <= 67.5)))
{
if ((sFiltered(i,j) < sFiltered(i-1,j+1)) || (sFiltered(i,j) < sFiltered(i+1,j-1)))
nonMaxSupped(i-1, j-1) = 0;
}
}
// cout << '\n';
}
}
高斯滤波去噪对边缘检测的影响,以及双阙值检测和连接边缘
void canny::threshold(CImg<unsigned char> imgin,int low, int high)
{
if(low > 255)
low = 255;
if(high > 255)
high = 255;
thres = CImg<unsigned char>(imgin._width, imgin._height);
for (int i=0; i<imgin._width; i++)
{
for (int j = 0; j<imgin._height; j++)
{
thres(i,j) = imgin(i,j);
if(thres(i,j) > high)
thres(i,j) = 255;
else if(thres(i,j) < low)
thres(i,j) = 0;
else
{
bool anyHigh = false;
bool anyBetween = false;
for (int x=i-1; x < i+2; x++)
{
for (int y = j-1; y<j+2; y++)
{
//a missing "x" in Hasan's code.
if(x <= 0 || y <= 0 || x > thres._width || y > thres._height) //Out of bounds
continue;
else
{
if(thres(x,y) > high)
{
thres(i,j) = 255;
anyHigh = true;
break;
}
else if(thres(x,y) <= high && thres(x,y) >= low)
anyBetween = true;
}
}
if(anyHigh)
break;
}
if(!anyHigh && anyBetween)
for (int x=i-2; x < i+3; x++)
{
for (int y = j-1; y<j+3; y++)
{
if(x < 0 || y < 0 || x > thres._width || y > thres._height) //Out of bounds
continue;
else
{
if(thres(x,y) > high)
{
thres(i,j) = 255;
anyHigh = true;
break;
}
}
}
if(anyHigh)
break;
}
if(!anyHigh)
thres(i,j) = 0;
}
}
}
}
最后关于将相邻边缘连成线条,同时也要注意删除长度小于20的线条。
CImg<int> canny::canny_line(CImg<int> picture, int distance) {
CImg<int> pic = picture;
//用于计算某一个像素点是否为边缘点
//判断方法为查看以这个点为中心的八邻域,如果附近只有1个像素点为0, 其他7个为255则是边缘点
bool isEdge[1000][1000];
cimg_forXY(pic, x, y) {
isEdge[x][y] = false;
if (x != rows - 1 && x != 0 && y != cols - 1 && y != 0 && pic(x, y) == 0) {
int linyu[8];
int m = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (!(i == x && j == y)) {
linyu[m] = pic(i, j);
m++;
}
}
}
sort(linyu, linyu + 8);
if (linyu[0] == 0 && linyu[1] == 255)
isEdge[x][y] = true;
}
}
cimg_forXY(pic, x, y) {
if (x >= distance && x <= rows - 1 - distance && y >= distance && y <= cols - 1 - distance && isEdge[x][y] == true) {
for (int i = x - distance; i <= x + distance; i++) {
for (int j = y - distance; j <= y + distance; j++) {
if (isEdge[i][j] == true) {
pic = Draw_line(pic, x, y, i, j);
isEdge[i][j] = false;
isEdge[x][y] = false;
}
}
}
}
}
return pic;
}
CImg<int> canny::delete_line(CImg<int> picture) {
//用于计算某一个像素点是否为边缘点
//判断方法为查看以这个点为中心的八邻域,如果附近只有1个像素点为0, 其他7个为255则是边缘点
CImg<int> pic = picture;
bool isEdge[1000][1000];
cimg_forXY(pic, x, y) {
isEdge[x][y] = false;
if (x != rows - 1 && x != 0 && y != cols - 1 && y != 0 && pic(x, y) == 0) {
int linyu[8];
int m = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (!(i == x && j == y)) {
linyu[m] = pic(i, j);
m++;
}
}
}
sort(linyu, linyu + 8);
if (linyu[0] == 0 && linyu[1] == 255)
isEdge[x][y] = true;
//删除单个孤立的点
if (linyu[0] == 255)
pic(x, y) = 255;
}
}
//删除长度少于20的连线
//判断如果两个边界点的距离小于20,就删除这两个边界点组成的矩阵内所有黑点,这样的话即使两个边界点分别是两条直线的话也无所谓
//反正是这样的话这两边界点之间都是白色区域,删除也无所谓
cimg_forXY(pic, x, y) {
int distance = 20;
if (isEdge[x][y] == true) {
int begin_x = x - distance > 0 ? x - distance : 0;
int begin_y = y - distance > 0 ? y - distance : 0;
int end_x = x + distance < rows - 1 ? x + distance : rows - 1;
int end_y = y + distance < cols - 1 ? y + distance : cols - 1;
for (int i = begin_x; i <= end_x; i++) {
for (int j = begin_y; j <= end_y; j++) {
if (isEdge[i][j] == true) {
int max_x = x >= i ? x : i;
int max_y = y >= j ? y : j;
int min_x = max_x == x ? i : x;
int min_y = max_y == y ? j : y;
for (int ii = min_x; ii <= max_x; ii++) {
for (int jj = min_y; jj <= max_y; jj++) {
pic(ii, jj) = 255;
}
}
isEdge[i][j] = false;
isEdge[x][y] = false;
}
}
}
}
}
//删除经过上一步处理完可能存在的单个孤立的噪声点
cimg_forXY(pic, x, y) {
if (x != rows - 1 && x != 0 && y != cols - 1 && y != 0 && pic(x, y) == 0) {
int linyu[8];
int m = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (!(i == x && j == y)) {
linyu[m] = pic(i, j);
m++;
}
}
}
sort(linyu, linyu + 8);
if (linyu[0] == 255)
pic(x, y) = 255;
}
}
return pic;
}
CImg<int> Draw_line(CImg<int> tmp, int x, int y, int x1, int y1) {
CImg <int> TempImg = tmp;
int black[] = { 0,0,0 };
TempImg.draw_line(x, y, x1, y1, black);
return TempImg;
}
开始时调整参数的效果图(以lena为例):
gsize = 3
gs = 3, g_sig = 1, threslo = 40, threshi = 60;
运行时界面:
gsize = 5
gs = 3, g_sig = 1, threslo = 40, threshi = 60;
gsize = 7
gs = 3, g_sig = 1, threslo = 40, threshi = 60;
运行时界面:
我们发现光改变sgize的值图像没有变化,我们改变阙值来进行观察。
sgize = 3
int gs = 3, g_sig = 1, threslo = 20, threshi = 40;
sgize = 3
int gs = 3, g_sig = 1, threslo = 4, threshi = 10;
明显可以看出当 threslo和threshi参数变小时,线条的密集程度更加复杂起来。
从这里我们可以得到以下结论:
高阈值减小,此时将会增加很多强边缘像素,因为大于选定这个阈值的
像素点都将被确定为边缘。
而高阈值增大,原来的强边缘像素大部分会转化为弱边缘像素,丢失一部分边缘像素点。这是因为被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。
参考目录:
[1]https://github.com/isVoid/CImg_Canny
[2]https://github.com/WangPerryWPY/Computer-Version
[3]https://github.com/hasanakg/Canny-Edge-Detector