目标
这里介绍一个人眼视网膜模型,它在图像预处理和增强方面展现了一些有趣的特性。在本教程中,您将学习如何
- 发现视网膜输出的两个主要通道
- 了解使用视网膜模型的基础知识
- 探索一些参数调整方法
概述
该模型源自 Jeanny Herault 在 Gipsa 的研究 [135]。它与 Listic(代码维护者和用户)实验室合作应用于图像处理。这不是一个完整的模型,但它已经展现出一些有趣的特性,可以用于增强图像处理体验。该模型允许使用以下人眼视网膜特性:
- 光谱白化,具有三个重要作用:消除高时空频率信号(噪声),增强中频细节和降低低频亮度能量。这种一体化特性可以直接消除图像传感器和输入亮度范围引入的经典不良失真。
- 局部对数亮度压缩,即使在低光照条件下也能增强细节。
- 细节信息(视锥细胞输出通道)和瞬态信息(事件,运动,在视杆细胞输出通道中可用)的去相关。
前两点如下图所示
下图显示了 OpenEXR 图像样本CrissyField.exr,一个高动态范围图像。为了使其在此网页上可见,原始输入图像被线性重新缩放至经典图像亮度范围[0-255],并转换为 8 位/通道格式。由于局部对比度过强,这种强转换隐藏了许多细节。此外,噪声能量也很强,污染了视觉信息。
图像
在下图中,应用 [24] 中提出的思想,就像你的视网膜一样,局部亮度适应、空间噪声去除和光谱白化协同工作,并在较低范围的 8 位数据通道上传输准确的信息。在这张图片中,噪声被显著去除,被强亮度对比度隐藏的局部细节得到了增强。输出图像保持其自然性,视觉内容得到增强。颜色处理基于 [67] 中提出的颜色复用/解复用方法。
图像
注意:图像样本可从 OpenEXR 网站 下载。关于此演示,在视网膜处理之前,输入图像已在线性缩放至 0-255 之间,并保持其通道浮点格式。其直方图的 5% 端已被裁剪(主要去除错误的 HDR 像素)。查看示例opencv/samples/cpp/OpenEXRimages_HighDynamicRange_Retina_toneMapping.cpp 以了解类似的处理。以下演示将只考虑经典的 8 位/通道图像。
视网膜模型输出通道
视网膜模型有两个输出,它们受益于上述行为。
- 第一个称为视锥细胞通道。它主要在中央凹视网膜区域活跃(具有颜色敏感感光细胞的高分辨率中央视觉),其目的是为视觉细节提供准确的颜色视觉,这些细节在视网膜上保持静止。另一方面,在视网膜投影上移动的物体是模糊的。
- 第二个众所周知的通道是视杆细胞通道。它主要在视网膜周边视觉中活跃,并发送与变化事件(运动、瞬态事件等)相关的信号。这些输出信号还有助于视觉系统将视网膜聚焦/集中在“瞬态”/移动区域,以进行更详细的分析,从而改进视觉场景上下文和物体分类。
注意:关于所提出的模型,与真实的视网膜相反,我们将这两个通道应用于整个输入图像,使用相同的分辨率。这允许在所有考虑的图像上提取增强的视觉细节和运动信息……但是请记住,这两个通道是互补的。例如,如果视杆细胞通道在一个区域产生强能量,那么视锥细胞通道在那里肯定会被模糊,因为存在瞬态事件。
作为示例,我们将在以下内容中将视网膜模型应用于黑暗视觉场景的网络摄像头视频流。在这个视觉场景中,在大学的圆形剧场拍摄,一些学生在与老师交谈时移动。
在这个视频序列中,由于环境昏暗,信噪比低,并且由于低质量图像采集工具链,视觉特征边缘存在颜色伪影。
图像
下面显示的是应用于整个图像的视网膜中央凹视觉。在使用的视网膜配置中,全局亮度保持不变,局部对比度得到增强。此外,信噪比得到改善:由于高频时空噪声被降低,增强的细节不会被任何增强的噪声破坏。
图像
以下是视网膜模型的视杆细胞输出。当发生瞬态事件时,其信号很强。在这里,一个学生在图像底部移动,从而产生高能量。图像的其余部分是静止的,然而,它被强噪声破坏。在这里,视网膜过滤掉大部分噪声,从而产生低误报运动区域“警报”。此通道可用作瞬态/移动区域检测器:它将为低成本分割工具提供相关信息,该工具将突出显示正在发生事件的区域。
图像
视网膜用例
该模型基本上可以用于时空视频效果,还可以用于
- 执行纹理分析,具有增强的信噪比和增强的细节,对输入图像亮度范围具有鲁棒性(查看视锥细胞视网膜通道输出)
- 执行运动分析,也利用前面提到的特性。
参考文献
更多信息,请参考以下论文:[24]
- 请参考Jeanny Herault的著作,可以在他的书中阅读[135]
此视网膜滤波器代码包含博士/研究同事的研究贡献,作者从中重新绘制了代码。
- 查看retinacolor.hpp模块,了解Brice Chaix de Lavarene博士的彩色马赛克/去马赛克算法及其参考文献[67]
- 查看imagelogpolprojection.hpp模块,了解源自Barthelemy Durette博士和Jeanny Herault的视网膜空间对数采样。还提出了视网膜/V1皮层投影,也源于Jeanny的讨论。更多信息请参见上面提到的Jeanny Herault的著作。
代码教程
请参考文件opencv_folder/samples/cpp/tutorial_code/bioinspired/retina_tutorial.cpp中的原始教程源代码。
- 注意
- 不要忘记视网膜模型包含在以下命名空间中:cv::bioinspired
要编译它,假设OpenCV已正确安装,请使用以下命令。它需要opencv_core(cv::Mat和友元对象管理)、opencv_highgui(显示和图像/视频读取)和opencv_bioinspired(视网膜描述)库进行编译。
// 编译
gcc retina_tutorial.cpp -o Retina_tuto -lopencv_core -lopencv_highgui -lopencv_bioinspired -lopencv_videoio -lopencv_imgcodecs
// 运行命令:添加“log”作为最后一个参数以应用空间对数采样(模拟视网膜采样)
// 在网络摄像头上运行
./Retina_tuto -video
// 在视频文件上运行
./Retina_tuto -video myVideo.avi
// 在图像上运行
./Retina_tuto -image myPicture.jpg
// 在图像上运行并进行对数采样
./Retina_tuto -image myPicture.jpg log
代码解释
视网膜定义存在于bioinspired包中,简单的包含即可使用它。如果您愿意,可以使用特定的头文件:opencv2/bioinspired.hpp,但是,您需要包含其他所需的opencv模块:opencv2/core.hpp和opencv2/highgui.hpp
#include "opencv2/opencv.hpp"
提供用户一些提示,使用帮助函数运行程序
static void help(std::string errorMessage)
{
std::cout<<"程序初始化错误:"<<errorMessage<<std::endl;
std::cout<<"\n程序调用过程:retinaDemo [处理模式] [可选:媒体目标] [可选最后一个参数:“log”以激活视网膜对数采样]"<<std::endl;
std::cout<<"\t[处理模式]:"<<std::endl;
std::cout<<"\t -image : 用于静态图像处理"<<std::endl;
std::cout<<"\t -video : 用于视频流处理"<<std::endl;
std::cout<<"\t[可选:媒体目标]:"<<std::endl;
std::cout<<"\t 如果处理图像或视频文件,则指定要处理的目标的路径和文件名"<<std::endl;
std::cout<<"\t 如果处理来自已连接视频设备的视频流,则留空"<<std::endl;
std::cout<<"\t[可选:激活视网膜对数采样]:可以指定一个可选的最后一个参数用于视网膜空间对数采样"<<std::endl;
std::cout<<"\t 设置“log”(不含引号)以激活此采样,输出帧大小将除以4"<<std::endl;
std::cout<<"\n示例:"<<std::endl;
std::cout<<"\t-图像处理:./retinaDemo -image lena.jpg"<<std::endl;
std::cout<<"\t-图像处理(带对数采样):./retinaDemo -image lena.jpg log"<<std::endl;
std::cout<<"\t-视频处理:./retinaDemo -video myMovie.mp4"<<std::endl;
std::cout<<"\t-实时视频处理:./retinaDemo -video"<<std::endl;
std::cout<<"\n请使用新参数重新开始"<<std::endl;
std::cout<<"****************************************************"<<std::endl;
std::cout<<" 注意:此程序生成默认视网膜参数文件'RetinaDefaultParameters.xml'"<<std::endl;
std::cout<<" =>您可以使用它来微调参数,如果您保存到文件'RetinaSpecificParameters.xml',则可以加载它们"<<std::endl;
}
然后,启动主程序,首先声明一个cv::Mat矩阵,其中将加载输入图像。还要分配一个cv::VideoCapture对象,准备加载视频流(如有必要)
int main(
int argc,
char* argv[]) {
用于从视频文件、图像序列或摄像机捕获视频的类。
定义 videoio.hpp:766
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
在主程序中,在处理之前,首先检查输入命令参数。在这里,它加载来自单个加载图像(如果用户选择命令-image)或视频流(如果用户选择命令-video)的第一个输入图像。此外,如果用户在其程序调用的末尾添加了log命令,则视网膜执行的空间对数图像采样将由布尔标志useLogSampling考虑。
std::cout<<"****************************************************"<<std::endl;
std::cout<<"* 视网膜演示:演示的是Gipsa/Listic实验室视网膜模型的包装类。"<<std::endl;
std::cout<<"* 此演示将尝试加载文件'RetinaSpecificParameters.xml'(如果存在)。\n要创建它,请复制自动生成的模板'RetinaDefaultParameters.xml'。\n然后使用您自己的视网膜参数对其进行调整。"<<std::endl;
if (argc<2)
{
help("参数数量错误");
return -1;
}
bool useLogSampling = !strcmp(argv[argc-1], "log");
std::string inputMediaType=argv[1];
if (!strcmp(inputMediaType.c_str(), "-image") && argc >= 3)
{
std::cout<<"RetinaDemo:正在处理图像 "<<argv[2]<<std::endl;
}else
if (!strcmp(inputMediaType.c_str(), "-video"))
{
if (argc == 2 || (argc == 3 && useLogSampling))
{
}else
{
std::cout<<"RetinaDemo: 正在处理视频流 "<<argv[2]<<std::endl;
videoCapture.
open(argv[2]);
}
videoCapture>>inputFrame;
}else
{
help("命令参数错误");
return -1;
}
virtual bool open(const String &filename, int apiPreference=CAP_ANY)
打开视频文件、采集设备或IP视频流进行视频采集。
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件中加载图像。
一旦所有输入参数都处理完毕,应该已经加载了第一张图像,如果没有,则显示错误并停止程序
{
help("无法加载输入媒体,正在中止");
return -1;
}
bool empty() const
如果数组没有元素,则返回true。
现在,一切准备就绪,可以运行视网膜模型了。这里建议分配一个视网膜实例并管理可能的日志采样选项。视网膜构造函数至少需要一个cv::Size对象,该对象显示必须管理的输入数据大小。可以激活其他选项,例如颜色及其相关的颜色多路复用策略(这里使用枚举cv::bioinspired::RETINA_COLOR_BAYER选择拜耳多路复用)。如果使用日志采样,可以调整图像缩减因子(较小的输出图像)和日志采样强度。
if (useLogSampling)
{
}
else
myRetina = cv::bioinspired::createRetina(inputFrame.
size());
MatSize size
定义 mat.hpp:2177
@ RETINA_COLOR_BAYER
标准拜耳采样
定义 retina.hpp:86
std::shared_ptr< _Tp > Ptr
定义 cvstd_wrapper.hpp:23
完成后,建议的代码会写入一个默认的xml文件,其中包含视网膜的默认参数。这对于使用此模板创建您自己的配置非常有用。此处生成的模板xml文件名为RetinaDefaultParameters.xml。
myRetina->write("RetinaDefaultParameters.xml");
在下面的代码行中,视网膜尝试加载另一个名为RetinaSpecificParameters.xml的xml文件。如果您创建了它并引入了您自己的设置,它将被加载,否则,将使用默认的视网膜参数。
myRetina->setup("RetinaSpecificParameters.xml");
这里不需要,只是为了表明这是可能的,您可以将视网膜缓冲区重置为零,以强制它忘记过去的事件。
myRetina->clearBuffers();
现在,是时候运行视网膜了!首先创建一些输出缓冲区,准备接收两个视网膜通道的输出
然后,在循环中运行视网膜,如有必要,从视频序列加载新帧并将视网膜输出返回到专用缓冲区。
while(true)
{
videoCapture>>inputFrame;
myRetina->run(inputFrame);
myRetina->getParvo(retinaOutput_parvo);
myRetina->getMagno(retinaOutput_magno);
}
virtual bool isOpened() const
如果视频采集已经初始化,则返回true。
void imshow(const String &winname, InputArray mat)
在指定的窗口中显示图像。
int waitKey(int delay=0)
等待按下按键。
完成了!但是如果您想保护系统,请注意并管理异常。当视网膜看到不相关的數據(无输入帧,设置错误等)时,它可能会抛出一些异常。然后,我建议使用try/catch系统包围所有视网膜代码,如下所示
try{
[---]
while(true)
{
[---]
}
{
std::cerr<<
"使用视网膜时出错:"<<e.
what()<<std::endl;
}
virtual const char * what() const noexcept override
视网膜参数,该怎么做?
首先,建议阅读参考文献[24]
完成后,打开演示生成的配置文件RetinaDefaultParameters.xml,让我们看一下。
<?xml version="1.0"?>
<opencv_storage>
<OPLandIPLparvo>
<colorMode>1</colorMode>
<normaliseOutput>1</normaliseOutput>
<photoreceptorsLocalAdaptationSensitivity>7.5e-01</photoreceptorsLocalAdaptationSensitivity>
<photoreceptorsTemporalConstant>9.0e-01</photoreceptorsTemporalConstant>
<photoreceptorsSpatialConstant>5.7e-01</photoreceptorsSpatialConstant>
<horizontalCellsGain>0.01</horizontalCellsGain>
<hcellsTemporalConstant>0.5</hcellsTemporalConstant>
<hcellsSpatialConstant>7.</hcellsSpatialConstant>
<ganglionCellsSensitivity>7.5e-01</ganglionCellsSensitivity></OPLandIPLparvo>
<IPLmagno>
<normaliseOutput>1</normaliseOutput>
<parasolCells_beta>0.</parasolCells_beta>
<parasolCells_tau>0.</parasolCells_tau>
<parasolCells_k>7.</parasolCells_k>
<amacrinCellsTemporalCutFrequency>2.0e+00</amacrinCellsTemporalCutFrequency>
<V0CompressionParameter>9.5e-01</V0CompressionParameter>
<localAdaptintegration_tau>0.</localAdaptintegration_tau>
<localAdaptintegration_k>7.</localAdaptintegration_k></IPLmagno>
</opencv_storage>
这里有一些提示,但实际上,最佳参数设置更多取决于您想如何使用视网膜,而不是您提供给视网膜的图像输入。除了需要针对特定亮度压缩目标进行更具体的设置的高动态范围图像 (HDR) 的特殊情况外,视网膜的行为应该在不同的内容之间保持相当稳定。请注意,OpenCV 能够通过 OpenEXR 图像兼容性来管理此类 HDR 格式。
然后,如果应用程序目标需要在特定图像处理之前增强细节,您需要知道是否需要平均亮度信息。如果不是,则视网膜可以取消或显著降低其能量,从而使更高空间频率的细节更清晰可见。
基本参数
最简单的参数如下:
- colorMode:让视网膜处理彩色信息(如果为 1)或灰度图像(如果为 0)。在后一种情况下,只会处理输入的第一个通道。
- normaliseOutput:每个通道都有此参数:如果将值设置为 1,则所考虑通道的输出将在 0 和 255 之间重新缩放。请注意这种情况下的 M 通道输出级别(运动/瞬态通道检测)。残余噪声也将被重新缩放!
注意:使用颜色需要颜色通道的多路复用/解复用,这也需要更多的处理。您可以预期使用灰度级可以获得更快的处理速度:它需要大约 30 次每像素乘法才能完成所有视网膜处理过程,并且最近已针对多核架构进行了并行化。
光感受器参数
以下参数作用于视网膜的入口点——光感受器——并影响所有后续过程。这些传感器是低通时空滤波器,可以平滑时空数据,并调整其对局部亮度的灵敏度,从而提高细节提取和高频噪声消除。
- photoreceptorsLocalAdaptationSensitivity 在 0 和 1 之间。接近 1 的值允许在光感受器级别实现高亮度对数压缩效果。接近 0 的值提供更线性的灵敏度。单独增加它可能会烧毁Parvo(细节通道)输出图像。如果与ganglionCellsSensitivity一起调整,则无论局部亮度如何,图像都可以非常对比……但代价是自然度下降。
- photoreceptorsTemporalConstant 此参数设置视网膜入口处的低通滤波器效应的时常数。较高的值会导致较强的时空平滑效果:移动物体变得模糊并可能消失,而静态物体则更有利。但是,当开始视网膜处理时,稳定状态的达到会延迟。
- photoreceptorsSpatialConstant 指定与光感受器低通滤波器效应相关的空间常数。这些参数指定后续允许的空间信号周期的最小值。通常,此滤波器应消除高频噪声。另一方面,0 值不会消除任何噪声,而较高的值则开始消除高空间频率,并逐渐消除较低的频率……如果您想看到输入图像的一些细节,请注意不要设置过高的值!对于彩色图像,一个好的折衷值为 0.53,因为这种选择不会过多地影响颜色光谱。较高的值会导致灰度和模糊的输出图像。
水平细胞参数
此参数集调整连接到光感受器的、水平细胞的神经网络。它调节光感受器的灵敏度并完成最终光谱白化处理(空间带通效应的一部分,从而有利于视觉细节增强)。
- horizontalCellsGain这是一个关键参数!如果您对平均亮度不感兴趣,只想关注细节增强,那么将此参数设置为零。但是,如果您想保留一些环境亮度数据,让一些低空间频率通过系统,并设置一个较高的值(<1)。
- hcellsTemporalConstant 与光感受器类似,此参数作用于平滑输入数据的低通时间滤波器的时常数。在这里,较高的值会产生较高的视网膜后效,而较低的值会使视网膜更具反应性。此值应低于photoreceptorsTemporalConstant,以限制强烈的视网膜后效。
- hcellsSpatialConstant 是这些细胞滤波器的低通滤波器的空间常数。它指定后续允许的最低空间频率。视觉上,较高的值会导致非常低的空间频率处理,并导致明显的晕影效应。较低的值会减少这种效应,但其限制是不低于photoreceptorsSpatialConstant的值。这两个参数实际上指定了视网膜的空间带通。
注意 一旦处理了前面参数管理的处理过程,输入数据就会被清除噪声,亮度也已部分增强。以下参数作用于两个输出视网膜信号的最后处理阶段。
Parvo(细节通道)专用参数
- ganglionCellsSensitivity 指定此细节专用通道输出处发生的最终局部适应的强度。参数值保持在 0 和 1 之间。低值倾向于给出线性响应,而较高的值则增强剩余的低对比度区域。
注意:此参数可以通过有利于视觉场景的低能量细节(即使在明亮区域)来校正最终烧毁的图像。
IPL Magno(运动/瞬态通道)参数
一旦图像信息被清除,该通道就会充当高通时间滤波器,仅选择与瞬态信号(事件、运动等)相关的信号。低通空间滤波器平滑提取的瞬态数据,而最终的对数压缩则增强低瞬态事件,从而增强事件灵敏度。
- parasolCells_beta 通常设置为零,可以被认为是此处理阶段入口点的放大器增益。通常设置为 0。
- parasolCells_tau 可以添加的时空平滑效果。
- parasolCells_k 空间滤波效果的空间常数,将其设置为高值以有利于较低空间频率信号,这些信号受残余噪声的影响较小。
- amacrinCellsTemporalCutFrequency 指定高通滤波器的时常数。较高的值允许选择慢瞬态事件。
- V0CompressionParameter 指定对数压缩的强度。与之前的描述类似的行为,但这里增强了瞬态事件的灵敏度。
- localAdaptintegration_tau 通常设置为 0,在此处实际上没有实际用途。
- localAdaptintegration_k 指定执行局部自适应的区域大小。较低的值会导致短程局部自适应(对噪声更敏感),较高的值确保对数压缩。