# 📏 Canny 邊緣檢測(Canny Edge Detection)
---
## 📘 1. 題目說明
Canny 邊緣檢測是一種經典且廣泛應用的圖像處理技術,用於檢測圖像中的邊緣特徵。其核心步驟包含:
1. **高斯模糊(Gaussian Blur)**
減少圖像中的雜訊與細節,避免誤檢邊緣。
2. **梯度計算(Computing Gradients)**
使用 Sobel 遮罩計算水平與垂直方向的梯度。
3. **非極大值抑制(Non-Maximum Suppression)**
保留梯度方向上的局部極大值,去除非邊緣像素。
4. **雙閾值檢測(Double Thresholding)**
根據梯度強度區分:強邊緣、弱邊緣與非邊緣。
5. **邊緣追蹤(Edge Tracking by Hysteresis)**
將與強邊緣連通的弱邊緣保留,其餘濾除。
---
## 🔁 2. 流程圖

---
## 💻 3. 程式碼邏輯與流程說明
### 🔧 A. 高斯低通濾波器
```cpp=
void gaussianLowPassFilter(int inputR[][MaxBMPSizeY], int outputR[][MaxBMPSizeY], int width, int height, float sigma)
{
// 計算高斯遮罩
int maskSize = 5;
float mask[maskSize][maskSize];
float sum = 0.0;
// 計算高斯遮罩的每個元素
for (int y = 0; y < maskSize; y++)
{
for (int x = 0; x < maskSize; x++)
{
mask[y][x] = exp(-(pow(x - maskSize / 2, 2) + pow(y - maskSize / 2, 2)) / (2 * sigma * sigma));
sum += mask[y][x];
}
}
//正規化
for (int y = 0; y < maskSize; y++)
{
for (int x = 0; x < maskSize; x++)
{
mask[y][x] /= sum;
}
}
for (int j = maskSize / 2; j < height - maskSize / 2; j++)
{
for (int i = maskSize / 2; i < width - maskSize / 2; i++)
{
float sumR = 0.0;
// 在遮罩範圍內計算像素值總和
for (int y = -maskSize / 2; y <= maskSize / 2; y++)
{
for (int x = -maskSize / 2; x <= maskSize / 2; x++)
{
sumR += inputR[i + x][j + y] * mask[y + maskSize / 2][x + maskSize / 2];
}
}
// 將相加結果設定為輸出影像的像素值
outputR[i][j] = (int)sumR;
}
}
}
```

- 使用 5×5 高斯遮罩
- 遮罩根據 `sigma` 產生並正規化(總和為 1)
- 每個像素套用遮罩後,完成平滑處理以降低雜訊
---
### B. 梯度計算(Sobel 遮罩)
- 使用 Sobel 遮罩計算圖像在 x 和 y 方向的梯度
```cpp=
void calculateGradient(int inputR[][MaxBMPSizeY], int outputMagnitude[][MaxBMPSizeY], int outputDirection[][MaxBMPSizeY], int width, int height)
{
// 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}
};
for (int j = 1; j < height - 1; j++) {
for (int i = 1; i < width - 1; i++) {
// 計算水平和垂直梯度
int gradientX = 0;
int gradientY = 0;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
gradientX += inputR[i + x][j + y] * sobelX[x + 1][y + 1];
gradientY += inputR[i + x][j + y] * sobelY[x + 1][y + 1];
}
}
// 計算梯度大小和方向
int magnitude = (int)sqrt(gradientX * gradientX + gradientY * gradientY);
double direction = atan2((double)gradientY, (double)gradientX) * 180 / M_PI;
// 將角度分成4等分
int quantizedDirection;
if ((direction >= -22.5 && direction < 22.5) || (direction <= -157.5 && direction >= -180)) {
quantizedDirection = 0;
}
else if (direction >= 22.5 && direction < 67.5) {
quantizedDirection = 45;
}
else if (direction >= 67.5 && direction < 112.5) {
quantizedDirection = 90;
}
else if (direction >= 112.5 && direction < 157.5) {
quantizedDirection = 135;
}
outputMagnitude[i][j] = magnitude; //梯度大小
outputDirection[i][j] = quantizedDirection; //梯度方向
}
}
}
```
### C. 非極大值抑制
- 對梯度大小圖像進行精細處理,保留局部最大值
- 根據梯度方向分為:0°、45°、90°、135°

```cpp=
void nonMaxSuppression(int inputMagnitude[][MaxBMPSizeY], int inputDirection[][MaxBMPSizeY], int outputR[][MaxBMPSizeY], int width, int height)
{
for (int j = 1; j < height - 1; j++) {
for (int i = 1; i < width - 1; i++) {
int magnitude = inputMagnitude[i][j]; // 當前像素的梯度強度
int direction = inputDirection[i][j]; // 當前像素的梯度方向
// 比較前後兩個方向的梯度強度,若不是最大值則設置為0
if ((direction == 0 && (magnitude < inputMagnitude[i][j + 1] || magnitude < inputMagnitude[i][j - 1])) ||
(direction == 45 && (magnitude < inputMagnitude[i - 1][j + 1] || magnitude < inputMagnitude[i + 1][j - 1])) ||
(direction == 90 && (magnitude < inputMagnitude[i + 1][j] || magnitude < inputMagnitude[i - 1][j])) ||
(direction == 135 && (magnitude < inputMagnitude[i + 1][j + 1] || magnitude < inputMagnitude[i - 1][j - 1])))
{
outputR[i][j] = 0; // 將非最大值設置為0
}
else {
outputR[i][j] = inputMagnitude[i][j]; // 保留最大值
}
}
}
}
```
### D. 雙閾值分類
- 高於高閾值 → 強邊緣
- 低於低閾值 → 非邊緣
- 介於兩者之間 → 弱邊緣

```cpp=
void doubleThreshold(int inputMagnitude[][MaxBMPSizeY], int outputR[][MaxBMPSizeY], int width, int height, int th, int tl)
{
//雙閾值判斷
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
int magnitude = inputMagnitude[i][j];
// 將梯度大小高於高閾值(th)的像素設定為強邊緣
if (magnitude > th) {
outputR[i][j] = 255;
}
// 將梯度大小低於低閾值(tl)的像素設定為非邊緣
else if (magnitude < tl) {
outputR[i][j] = 0;
}
// 將梯度大小介於高閾值和低閾值之間的像素標記為可能的邊緣
else {
outputR[i][j] = 128; // 使用128作為標記值,之後會進行邊緣追蹤
}
}
}
```
### E. 邊緣追蹤
- 若弱邊緣與強邊緣連接 → 視為邊緣
- 其餘弱邊緣 → 濾除

```cpp=
//邊緣追蹤(edge tracking)
for (int j = 1; j < height - 1; j++) {
for (int i = 1; i < width - 1; i++) {
// 檢查標記值為128的像素(可能的邊緣)
if (outputR[i][j] == 128) {
// 檢查周圍像素是否有強邊緣(標記值為255)
bool hasStrongEdge = false;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
if (outputR[i + x][j + y] == 255) {
hasStrongEdge = true;
break;
}
}
if (hasStrongEdge) {
break;
}
}
// 如果周圍像素中有強邊緣,則將該像素設定為強邊緣(255),否則設定為非邊緣(0)
outputR[i][j] = hasStrongEdge ? 255 : 0;
}
}
}
}
```
### ✅ 3. 實驗結果與分析
---
#### 📷 處理前後影像對比
| 原始圖像 | Canny 邊緣偵測後 |
|----------|------------------|
| |
---
#### 📊 分析說明
- **精準定位邊緣:**
相較於其他邊緣偵測演算法,Canny 能更準確地描繪出物體輪廓,提升邊緣偵測的精度。
- **雜訊抑制效果佳:**
結合高斯濾波與雙閾值處理,有效濾除影像中的隨機雜訊,降低誤偵測率。
- **邊緣連續性良好:**
透過弱邊緣追蹤機制(Hysteresis Thresholding),確保邊緣在偵測結果中連續不斷裂,使影像輪廓更自然清晰。
---
#### 📈 結論
- Canny 邊緣偵測法在保留物體結構的同時,有效去除雜訊。
- 適合應用於需要高邊緣解析度的圖像前處理階段。
- 若搭配形態學處理(如膨脹、腐蝕)可進一步強化邊緣輪廓。