在百度百科神经节细胞介绍中有这么一段话:
当均匀一致的光线照到全部视网膜时(即全部光感受器都被入射的光线同等地兴奋时),对比类型的神经节细胞就既不兴奋也不抑制。这是因为从光感受器通过双极细胞直接传来的信号是兴奋性的,而从周围的光感受器通过水平细胞传来的信号是抑制的。这两个反应就互相抵销。相反,如果有个对比边缘,边缘一侧的光感受器兴奋,边缘另一侧的光感受器不兴奋,就没有直接通过双极细胞的兴奋信号和通过水平细胞抑制信号的抵销。其结果是在亮的那边的边缘上的神经节细胞兴奋,而暗的那侧的则被抑制。
我们尝试使用这个原理进行图像的边缘检测,请看下图。
图1图1可看作一部分视网膜的电子版,B代表双极细胞,H代表水平细胞,双极细胞和水平细胞间隔排列,它们和图片像素数量一致。我们把图像数据映射到所有的细胞,然后我们从所有细胞获得输出信号。
假设B、H、H输入的三个点都是白色像素,那么 B是兴奋的,H是抑制的。B点输入像素的值为RGB(255,255,255),那么B的输出为RGB(255,255,255),两个H点的输入像素为RGB(255,255,255),则输出都为RGB(0, 0, 0)。
这里我们从粗体的B像素点(1,1)来读取输出值,因为B和H数量要一致才能互相抵消,我们将B(1,1)点读取两次,H(1,2)和H(2,1)分别读取一次,那么针对像素点(1,1),我们得到以下输出结果:
RGB(255,255,255)
RGB(255,255,255)
RGB(0, 0, 0)
RGB(0, 0, 0)
将以上四个结果相加然后除以4,便得到像素点(1,1)的输出值:
RGB(0, 0, 0)
此输出说明没有检测到边缘。
假设B点是白色像素,两个H点是黑色像素,那么 B是兴奋的,H也是兴奋的。4次读取将得到一下输出结果:
RGB(255,255,255)
RGB(255,255,255)
RGB(255, 255, 255)
RGB(255, 255, 255)
同样,将以上四个结果相加然后除以4,得到像素点(1,1)的输出值:
RGB(255, 255, 255)
此输出说明检测到了边缘。
接下来要提取整张图片的边缘,就很简单了,循环一下每个像素即可,下面是代码:
extern crate lodepng;
use lodepng::RGB;
fn main() {
let bitmap = lodepng::decode24_file("rust.png").unwrap();
let (width, height) = (bitmap.width, bitmap.height);
println!("width={},height={}", width, height);
let buffer = edge_detection(&bitmap.buffer, width, height, 127.5);
lodepng::encode24_file("rust_out.png", &buffer, width, height).unwrap();
}
//边缘检测
//buffer: 图像数据
//height: 图像高度
//width: 图像宽度
//threshold: 阈值0~255
//返回: 黑底百色边缘的图像数据
fn edge_detection(buffer:&Vec<RGB<u8>>, width:usize, height:usize, threshold:f32)->Vec<RGB<u8>>{
let mut out_buffer:Vec<RGB<u8>> = vec![];
let mut i = 0;
for _row in 0..height{
for _col in 0..width{
//4个像素
//双极细胞 给光ON,撤光OFF => 超过阈值:255
//水平细胞 亮光抑制,弱光增强,和双极细胞正好相反 => 超过阈值:-255
match((calc_pixel(buffer.get(i).unwrap_or(&buffer[i]), 255.0, threshold)
+ calc_pixel(buffer.get(i+1).unwrap_or(&buffer[i]), -255.0, threshold)
+calc_pixel(buffer.get(i).unwrap_or(&buffer[i]), 255.0, threshold)
+ calc_pixel(buffer.get(i+width).unwrap_or(&buffer[i]), -255.0, threshold)
)/4.0) as i32{ //(Sum(..pixel..)/255*4)*255 = Sum(..pixel..)/4
0 => out_buffer.push(RGB::new(0, 0, 0)),
_ => out_buffer.push(RGB::new(255, 255, 255)),
}
i += 1;
}
}
out_buffer
}
//计算每个像素的输出
// p: 像素
// out: 超过阈值细胞的输出
// threshold: 阈值0~255
fn calc_pixel(pixel:&RGB<u8>, out:f32, threshold:f32)->f32{
//二值化以后根据双极细胞、水平细胞返回输出值
if 0.299*pixel.r as f32+0.587*pixel.g as f32+0.114*pixel.b as f32>threshold{
out
}else{
0.0 //弱光都不返回
}
}
代码运行效果如下:
图2 原图(Rust的logo) 图3 输出(检测出的边缘用白色像素显示)再看另一个:
图4 原图(身份证上拍摄的国徽) 图5 输出项目的源码地址:https://github.com/planet0104/edge_detection
(原创文章)
2018年1月30日 上海
planet2@qq.com
网友评论