計算機視覺實驗報告
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