怎么做Canny边缘检测的第一步——边缘强度?
- 使用高斯滤波;
- 在x方向和y方向上使用Sobel滤波器,在此之上求出边缘的强度和边缘的梯度;
- 对梯度幅值进行非极大值抑制(Non-maximum suppression)来使边缘变得更细;
- 使用滞后阈值来对阈值进行处理。
上面就是图像边缘检测的方法了。在这里我们先完成第一步和第二步。按照以下步骤进行处理:
1. 将图像进行灰度化处理;
- 将图像进行高斯滤波(5\times5,s=1.4);
-
在x方向和y方向上使用Sobel滤波器,在此之上求出边缘梯度f_x和f_y。边缘梯度可以按照下式求得:
\text{edge}=\sqrt{{f_x}^2+{f_x}^2}\\
\text{tan}=\arctan(\frac{f_y}{f_x}) -
使用下面的公式将梯度方向量化:
\text{angle}=\begin{cases}
0&(\text{if}\quad -0.4142<\tan \leq 0.4142)\\ 45&(\text{if}\quad 0.4142<\tan<2.4142)\\ 90 &(\text{if}\quad |\tan| \geq 2.4142)\\ 135 &(\text{if}\quad -2.4142<\tan \leq -0.4142) \end{cases}
请使用numpy.pad()
来设置滤波器的padding
吧!
python实现:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def Canny_step1(img):
# Gray scale
def BGR2GRAY(img):
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# Gray scale
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
return out
# Gaussian filter for grayscale
def gaussian_filter(img, K_size=3, sigma=1.3):
if len(img.shape) == 3:
H, W, C = img.shape
gray = False
else:
img = np.expand_dims(img, axis=-1)
H, W, C = img.shape
gray = True
## Zero padding
pad = K_size // 2
out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float)
out[pad: pad + H, pad: pad + W] = img.copy().astype(np.float)
## prepare Kernel
K = np.zeros((K_size, K_size), dtype=np.float)
for x in range(-pad, -pad + K_size):
for y in range(-pad, -pad + K_size):
K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * (sigma ** 2)))
K /= (2 * np.pi * sigma * sigma)
K /= K.sum()
tmp = out.copy()
# filtering
for y in range(H):
for x in range(W):
for c in range(C):
out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c])
out = np.clip(out, 0, 255)
out = out[pad : pad + H, pad : pad + W]
#out = out.astype(np.uint8)
if gray:
out = out[..., 0]
return out
# sobel filter
def sobel_filter(img, K_size=3):
if len(img.shape) == 3:
H, W, C = img.shape
else:
#img = np.expand_dims(img, axis=-1)
H, W = img.shape
# Zero padding
pad = K_size // 2
out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float)
out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)
tmp = out.copy()
out_v = out.copy()
out_h = out.copy()
## Sobel vertical
Kv = [[1., 2., 1.],[0., 0., 0.], [-1., -2., -1.]]
## Sobel horizontal
Kh = [[1., 0., -1.],[2., 0., -2.],[1., 0., -1.]]
# filtering
for y in range(H):
for x in range(W):
out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size]))
out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size]))
out_v = np.clip(out_v, 0, 255)
out_h = np.clip(out_h, 0, 255)
out_v = out_v[pad : pad + H, pad : pad + W].astype(np.uint8)
out_h = out_h[pad : pad + H, pad : pad + W].astype(np.uint8)
return out_v, out_h
def get_edge_angle(fx, fy):
# get edge strength
edge = np.sqrt(np.power(fx, 2) + np.power(fy, 2))
fx = np.maximum(fx, 1e-5)
# get edge angle
angle = np.arctan(fy / fx)
return edge, angle
def angle_quantization(angle):
angle = angle / np.pi * 180
angle[angle < -22.5] = 180 + angle[angle < -22.5]
_angle = np.zeros_like(angle, dtype=np.uint8)
_angle[np.where(angle <= 22.5)] = 0
_angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45
_angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90
_angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135
return _angle
# grayscale
gray = BGR2GRAY(img)
# gaussian filtering
gaussian = gaussian_filter(gray, K_size=5, sigma=1.4)
# sobel filtering
fy, fx = sobel_filter(gaussian, K_size=3)
# get edge strength, angle
edge, angle = get_edge_angle(fx, fy)
# angle quantization
angle = angle_quantization(angle)
return edge, angle
# Read image
img = cv2.imread("imori.jpg").astype(np.float32)
# Canny (step1)
edge, angle = Canny_step1(img)
edge = edge.astype(np.uint8)
angle = angle.astype(np.uint8)
# Save result
cv2.imwrite("out.jpg", edge)
cv2.imshow("result", edge)
cv2.imwrite("out2.jpg", angle)
cv2.imshow("result2", angle)
cv2.waitKey(0)
cv2.destroyAllWindows()
c++实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// RGB to Gray scale
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// BGR -> Gray
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
out.at<uchar>(y, x) = (int)((float)img.at<cv::Vec3b>(y, x)[0] * 0.0722 + \
(float)img.at<cv::Vec3b>(y, x)[1] * 0.7152 + \
(float)img.at<cv::Vec3b>(y, x)[2] * 0.2126);
}
}
return out;
}
float clip(float value, float min, float max){
return fmin(fmax(value, 0), 255);
}
// gaussian filter
cv::Mat gaussian_filter(cv::Mat img, double sigma, int kernel_size){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);
if (channel == 1) {
out = cv::Mat::zeros(height, width, CV_8UC1);
}
// prepare kernel
int pad = floor(kernel_size / 2);
int _x = 0, _y = 0;
double kernel_sum = 0;
// get gaussian kernel
float kernel[kernel_size][kernel_size];
for (int y = 0; y < kernel_size; y++){
for (int x = 0; x < kernel_size; x++){
_y = y - pad;
_x = x - pad;
kernel[y][x] = 1 / (2 * M_PI * sigma * sigma) * exp( - (_x * _x + _y * _y) / (2 * sigma * sigma));
kernel_sum += kernel[y][x];
}
}
for (int y = 0; y < kernel_size; y++){
for (int x = 0; x < kernel_size; x++){
kernel[y][x] /= kernel_sum;
}
}
// filtering
double v = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// for BGR
if (channel == 3){
for (int c = 0; c < channel; c++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((x + dx) >= 0) && ((y + dy) >= 0) && ((x + dx) < width) && ((y + dy) < height)){
v += (double)img.at<cv::Vec3b>(y + dy, x + dx)[c] * kernel[dy + pad][dx + pad];
}
}
}
out.at<cv::Vec3b>(y, x)[c] = (uchar)clip(v, 0, 255);
}
} else {
// for Gray
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((x + dx) >= 0) && ((y + dy) >= 0) && ((x + dx) < width) && ((y + dy) < height)){
v += (double)img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
out.at<uchar>(y, x) = (uchar)clip(v, 0, 255);
}
}
}
return out;
}
// Sobel filter
cv::Mat sobel_filter(cv::Mat img, int kernel_size, bool horizontal){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
double kernel[kernel_size][kernel_size] = {{1, 2, 1}, {0, 0, 0}, {-1, -2, -1}};
if (horizontal){
kernel[0][1] = 0;
kernel[0][2] = -1;
kernel[1][0] = 2;
kernel[1][2] = -2;
kernel[2][0] = 1;
kernel[2][1] = 0;
}
int pad = floor(kernel_size / 2);
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += (double)img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
out.at<uchar>(y, x) = (uchar)clip(v, 0, 255);
}
}
return out;
}
// get edge
cv::Mat get_edge(cv::Mat fx, cv::Mat fy){
// get height and width
int height = fx.rows;
int width = fx.cols;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
double _fx, _fy;
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
_fx = (double)fx.at<uchar>(y, x);
_fy = (double)fy.at<uchar>(y, x);
out.at<uchar>(y, x) = (uchar)clip(sqrt(_fx * _fx + _fy * _fy), 0, 255);
}
}
return out;
}
// get angle
cv::Mat get_angle(cv::Mat fx, cv::Mat fy){
// get height and width
int height = fx.rows;
int width = fx.cols;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
double _fx, _fy;
double angle;
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
_fx = fmax((double)fx.at<uchar>(y, x), 0.000001);
_fy = (double)fy.at<uchar>(y, x);
angle = atan2(_fy, _fx);
angle = angle / M_PI * 180;
if(angle < -22.5){
angle = 180 + angle;
} else if (angle >= 157.5) {
angle = angle - 180;
}
//std::cout << angle << " " ;
// quantization
if (angle <= 22.5){
out.at<uchar>(y, x) = 0;
} else if (angle <= 67.5){
out.at<uchar>(y, x) = 45;
} else if (angle <= 112.5){
out.at<uchar>(y, x) = 90;
} else {
out.at<uchar>(y, x) = 135;
}
}
}
return out;
}
// Canny step 1
int Canny_step1(cv::Mat img){
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// gaussian filter
cv::Mat gaussian = gaussian_filter(gray, 1.4, 5);
// sobel filter (vertical)
cv::Mat fy = sobel_filter(gaussian, 3, false);
// sobel filter (horizontal)
cv::Mat fx = sobel_filter(gaussian, 3, true);
// get edge
cv::Mat edge = get_edge(fx, fy);
// get angle
cv::Mat angle = get_angle(fx, fy);
//cv::imwrite("out.jpg", out);
cv::imshow("answer(edge)", edge);
cv::imshow("answer(angle)", angle);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// Canny step 1
Canny_step1(img);
return 0;
}
输入:
输出(梯度幅值):
输出(梯度方向):