1. 程式人生 > >計算機視覺 || Canny運算元實現邊緣分割並進一步處理

計算機視覺 || Canny運算元實現邊緣分割並進一步處理

Ex2:用 CImg 重寫、封裝給定的 Canny 程式碼,並測試

C++封裝要求:

(1)所有的影象讀寫、資料處理只能用 CImg 庫(整個工程檔案不允許
使用 Opencv 之類的第三方庫); (2)程式碼封裝要求函式介面簡潔清晰,可參考
Code2 的方式封裝。
在原來的程式碼基礎上, 增加一個函式:首先把相鄰的邊緣連成長的線條,並刪除
長度小於 20 的 Edge。
對演算法的若干組引數,對所有測試影象進行測試,並分析各引數對結果的影響。

實現過程:

原理:
實現 canny 邊緣檢測的原理通俗來說就是用離散化的梯度逼近函式根據二維灰度矩陣梯度向量來尋找影象灰度矩陣的灰度躍變位置,然後再影象中將這些點連起來就形成了影象的邊緣。
處理過程:


一般對影象進行 canny 邊緣檢測要經過以下幾步:

  1. 對影象進行灰度化處理:
    公式: Gimg = 0.299R+0.587G+0.114B
    void RGBtoGray(); 
  1. 對影象進行高斯濾波(出去圖片中噪聲對邊緣檢測的影響)
    void gaussian_smooth(float sigma); 

用到輔助函式:
void make_gaussian_kernel(float sigma, float **kernel, int *windowsize);
3. 用一階偏導的有限差分來計算梯度的幅值和方向

    //計算x,y方向的一階導數 
void derrivative_x_y();

用到輔助函式:
double angle_radians(double x, double y);

    //計算梯度向上的方向,以正x軸為逆時針方向指定的弧度 
    void radian_direction(int xdirtag, int ydirtag); 
    //計算梯度的幅值 
    void magnitude_x_y(); 
  1. 對梯度幅值進行非極大值抑制
    void non_max_supp(); 
  1. 雙閾值檢測和連線邊緣
    void apply_hysteresis
(float tlow, float thigh);

用到輔助函式:
void follow_edges(int *edgemapptr, int *edgemagptr, int lowval, int cols);

  1. 之後再對將相鄰邊緣連成線條:
    CImg<int> canny_line(CImg<int> picture, int distance); 

用到輔助函式:
CImg<int> Draw_line(CImg<int> tmp, int x, int y, int x1, int y1);

  1. 刪除長度小於20的線條:
    CImg<int> delete_line(CImg<int> picture); CImg<int> delete_line(CImg<int> picture); 

程式碼:

Canny.h

/*實現canny邊緣檢測的原理通俗來說就是用離散化的梯度逼近函式根據二維灰度矩陣梯度向量來尋找影象灰度矩陣
的灰度躍變位置,然後再影象中將這些點連起來就形成了影象的邊緣
一般對影象進行canny邊緣檢測要經過以下幾步:
1.對影象進行灰度化處理:Gimg = 0.299R+0.587G+0.114B
2.對影象進行高斯濾波(出去圖片中噪聲對邊緣檢測的影響)
3.用一階偏導的有限差分來計算梯度的幅值和方向
4.對梯度幅值進行非極大值抑制
5.雙閾值檢測和連線邊緣
*/

#ifndef CANNY_H_
#define CANNY_H_
#include <iostream>
#include "CImg.h"
#include <cmath>

using namespace std;
using namespace cimg_library;

class Canny {
private:
	CImg<int> img;
	int rows;
	int cols;
	int *smoothedim;
	int *delta_x;  //x方向的一階導數
	int *delta_y;  //y方向的一階導數
	float *dirim;  //梯度的方向
	int *magnitude; //梯度的幅值
	int *nms;   //非極大值抑制後得到矩陣
	int *edge;  //邊緣陣列
public:
	Canny();
	Canny(string name,string format);
	~Canny();
	void RGBtoGray();
	void gaussian_smooth(float sigma);
	//計算x,y方向的一階導數
	void derrivative_x_y();
	//計算梯度向上的方向,以正x軸為逆時針方向指定的弧度
	void radian_direction(int xdirtag, int ydirtag);
	//計算梯度的幅值
	void magnitude_x_y();
	//進行非極大值抑制
	void non_max_supp();
	//雙閾值檢測
	void apply_hysteresis(float tlow, float thigh);
	//預設引數設定高斯濾波標準差為2.0,低閾值為0.25,高閾值為0.75
	CImg<int> canny_image();
	//整合所有獲取最後的邊緣圖,sigma表示高斯濾波的引數,tlow和thigh為兩個閾值
	CImg<int> canny_image(int sigma, float tlow, float thigh);
	//選出兩個邊緣點較近的距離連線
	CImg<int> canny_line(CImg<int> picture, int distance);
	//刪掉長度小於20的邊緣線
	CImg<int> delete_line(CImg<int> picture);
	//顯示影象
	void display();
	//編寫類過程的測試,無實際用處
	void test();
};

#endif

Canny.cpp

#include "pch.h"
#include "Canny.h"
#include <iostream>
#include "CImg.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#define pi 3.1415926
#define M_PI 3.14159265358979323846
#define BOOSTBLURFACTOR 90.0
#define NOEDGE 255
#define POSSIBLE_EDGE 128
#define EDGE 0
using namespace std;
using namespace cimg_library;
void make_gaussian_kernel(float sigma, float **kernel, int *windowsize);
double angle_radians(double x, double y);
void follow_edges(int *edgemapptr, int *edgemagptr, int lowval, int cols);
CImg<int> Draw_line(CImg<int> tmp, int x, int y, int x1, int y1);

Canny::Canny() {
	rows = 0;
	cols = 0;
	smoothedim = NULL;
	delta_x = NULL;  //x方向的一階導數
	delta_y = NULL;  //y方向的一階導數
	dirim = NULL;  //梯度的方向
	magnitude = NULL; //梯度的幅值
	nms = NULL;   //非極大值抑制後得到矩陣
	edge = NULL; //邊緣陣列
}

Canny::Canny(string name, string format) {
	const char *a = name.c_str();
	string jpg = "jpg";
	string png = "png";
	string tiff = "tiff";
	string bmp = "bmp";
	if (format.compare(jpg) == 0)
		img.load_jpeg(a);
	else if (format.compare(png) == 0)
		img.load_png(a);
	else if (format.compare(tiff) == 0)
		img.load_tiff(a);
	else if (format.compare(bmp) == 0)
		img.load_bmp(a);
	else
		exit(-1);
	//img.load_jpeg("lena.jpg");
	rows = img.width();
	cols = img.height();
	delta_x = new int[rows*cols]; memset(delta_x, 0x0, rows*cols*sizeof(int));
	delta_y = new int[rows*cols]; memset(delta_y, 0x0, rows*cols * sizeof(int));
	dirim = new float[rows*cols]; memset(dirim, 0x0, rows*cols * sizeof(float));
	magnitude = new int[rows*cols]; memset(magnitude, 0x0, rows*cols * sizeof(int));
	nms = new int[rows*cols]; memset(nms, 0x0, rows*cols * sizeof(int));
	edge = new int[rows*cols]; memset(edge, 0x0, rows*cols * sizeof(int));
	smoothedim = new int[rows*cols];  memset(smoothedim, 0x0, rows*cols * sizeof(int));
}

Canny::~Canny() {
	delete[] delta_x;
	delete[] delta_y;
	delete[] dirim;
	delete[] magnitude;
	delete[] nms;
	delete[] edge;
	delete[] smoothedim;
}

void Canny::display() {
	img.display();
}

void Canny::RGBtoGray() {
	int r = 0, g = 0, b = 0;
	cimg_forXY(img, x, y) {
		r = img(x, y, 0);
		g = img(x, y, 1);
		b = img(x, y, 2);
		img(x, y, 0) = img(x, y, 1) = img(x, y, 2) = (r*0.2126 + g * 0.7152 + b * 0.0722);
	}
	img.resize(rows, cols, 1, 1, 5);
}

void Canny::gaussian_smooth(float sigma)
{
	int *tempim = new int[rows*cols];
	int r, c,rr,cc,
		windowsize,  //高斯核維度
		center;      //核中心
	float *kernel,
		dot,
		sum;
	make_gaussian_kernel(sigma, &kernel, &windowsize);
	center = windowsize / 2;
	for (r = 0; r < rows; r++) {
		for (c = 0; c < cols; c++) {
			dot = 0.0;
			sum = 0.0;
			for (cc = (-center); cc <= center; cc++) {
				if (((c + cc) >= 0) && ((c + cc) < cols)) {
					dot += (float)img(r,c+cc) * kernel[center + cc];
					sum += kernel[center + cc];
				}
			}
			tempim[r*cols + c] = dot / sum;
		}
	}
	for (c = 0; c < cols; c++) {
		for (r = 0; r < rows; r++) {
			sum = 0.0;
			dot = 0.0;
			for (rr = (-center); rr <= center; rr++) {
				if (((r + rr) >= 0) && ((r + rr) < rows)) {
					dot += tempim[(r + rr)*cols + c] * kernel[center + rr];
					sum += kernel[center + rr];
				}
			}
			smoothedim[r*cols + c] = (short int)(dot*BOOSTBLURFACTOR / sum + 0.5);
		}
	}
}

void Canny::derrivative_x_y() {
	int r = 0, c = 0, pos = 0;
	//計算x方向的一階導數,判斷邊界避免遺失邊界畫素點
	for (r = 0; r < rows; r++) {
		pos = r * cols;
		delta_x[pos] = smoothedim[pos + 1] - smoothedim[pos];
		pos++;
		for (c = 1; c < (cols - 1); c++, pos++) {
			delta_x[pos] = smoothedim[pos + 1] - smoothedim[pos - 1];
		}
		delta_x[pos] = smoothedim[pos] - smoothedim[pos - 1];
	}
	//計算y方向的一階導數,判斷邊界避免遺失邊界畫素點
	for (c = 0; c < cols; c++) {
		pos = c;
		delta_y[pos] = smoothedim[pos + cols] - smoothedim[pos];
		pos += cols;
		for (r = 1; r < (rows - 1); r++, pos += cols) {
			delta_y[pos] = smoothedim[pos + cols] - smoothedim[pos - cols];
		}
		delta_y[pos] = smoothedim[pos] - smoothedim[pos - cols];
	}
}

void Canny::radian_direction(int xdirtag, int ydirtag) {
	double dx = 0.0, dy = 0.0;
	int r = 0, c = 0, pos = 0;
	for (r = 0, pos = 0; r < rows; r++) {
		for (c = 0; c < cols; c++, pos++) {
			dx = (double)delta_x[pos];
			dy = (double)delta_y[pos];

			if (xdirtag == 1) dx = -dx;
			if (ydirtag == -1) dy = -dy;

			dirim[pos] = (float)angle_radians(dx, dy);
		}
	}
}

void Canny::magnitude_x_y() {
	int r = 0, c = 0, pos = 0, sq1 = 0, sq2 = 0;
	for (r = 0, pos = 0; r < rows; r++) {
		for (c = 0; c < cols; c++, pos++) {
			sq1 = (int)delta_x[pos] * (int)delta_x[pos];
			sq2 = (int)delta_y[pos] * (int)delta_y[pos];
			magnitude[pos] = (short)(0.5 + sqrt((float)sq1 + (float)sq2));
		}
	}
}

void Canny::non_max_supp() {
	int rowcount = 0, colcount = 0, count = 0;
	int *magrowptr, *magptr;
	int *gxrowptr, *gxptr;
	int *gyrowptr, *gyptr, z1 = 0, z2 = 0;
	int m00, gx = 0, gy = 0;
	float mag1 = 0.0, mag2 = 0.0, xperp = 0.0, yperp = 0.0;
	int *resultrowptr, *resultptr;

	for (count = 0, resultrowptr = nms, resultptr = nms + cols * (rows - 1);
		count < cols; resultptr++, resultrowptr++, count++) {
		*resultrowptr = *resultptr = 0;
	}

	for (count = 0, resultptr = nms, resultrowptr = nms + cols - 1;
		count < rows; count++, resultptr += cols, resultrowptr +