目标
本文介绍了一个人类视网膜模型,该模型在图像预处理和增强方面展现出一些有趣的特性。在本教程中,您将学习如何
- 发现视网膜的两个主要输出通道
- 了解使用视网膜模型的基础知识
- 探索一些参数调整
概述
所提出的模型源于 Jeanny Herault 在 [138] 的 Gipsa 实验室的研究。它与 Listic(代码维护者和用户)实验室共同应用于图像处理。虽然这不是一个完整的模型,但它已经展现出有趣的特性,可用于增强图像处理体验。该模型允许使用以下人类视网膜特性:
- 光谱白化,具有三个重要效果:消除高时空频率信号(噪声),增强中频细节,并降低低频亮度能量。这种一体化特性直接允许对图像传感器和输入亮度范围引入的经典不希望的失真进行视觉信号清理。
- 局部对数亮度压缩允许即使在弱光条件下也能增强细节。
- 细节信息(小细胞输出通道)与瞬态信息(在大细胞输出通道可用的事件、运动)的去相关。
前两点在下面进行了说明:
在下图中,显示了OpenEXR图像样本CrissyField.exr,这是一张高动态范围图像。为了使其在此网页上可见,原始输入图像被线性缩放到经典的图像亮度范围[0-255],并转换为8位/通道格式。这种强烈的转换由于局部对比度过高而隐藏了许多细节。此外,噪声能量也很强,污染了视觉信息。
image
在下图中,应用了[25]中提出的思想,如同您的视网膜一样,局部亮度适应、空间去噪和光谱白化协同工作,在较低范围的8位数据通道上传输准确信息。在这张图片中,噪声被显著消除,被强烈亮度对比度隐藏的局部细节得到增强。输出图像保持其自然性,视觉内容得到提升。颜色处理基于[69]中提出的颜色多路复用/解复用方法。
image
注:图像样本可从OpenEXR网站下载。关于此演示,在视网膜处理之前,输入图像已线性缩放到0-255范围,并保留其通道浮点格式。其直方图末端5%已被截断(主要用于去除错误的HDR像素)。请查看样本opencv/samples/cpp/OpenEXRimages_HighDynamicRange_Retina_toneMapping.cpp以了解类似的处理。以下演示将仅考虑经典的8位/通道图像。
视网膜模型输出通道
视网膜模型提供两个输出,它们都受益于上述行为。
- 第一个被称为小细胞通道(Parvocellular channel)。它主要活跃在视网膜中央凹区域(具有颜色敏感感光器的高分辨率中央视觉),其目的是为在视网膜上保持静态的视觉细节提供准确的彩色视觉。另一方面,在视网膜投影上移动的物体会变得模糊。
- 第二个众所周知的通道是大细胞通道(Magnocellular channel)。它主要活跃在视网膜周边视觉区域,并发送与变化事件(运动、瞬态事件等)相关的信号。这些输出信号还有助于视觉系统将视网膜聚焦/定位于“瞬态”/移动区域,以进行更详细的分析,从而改善视觉场景上下文和对象分类。
注意:关于所提出的模型,与真实视网膜不同的是,我们将这两个通道以相同的分辨率应用于整个输入图像。这使得所有考虑的图像都能提取增强的视觉细节和运动信息……但请记住,这两个通道是互补的。例如,如果大细胞通道在一个区域提供强能量,那么该区域的小细胞通道肯定会模糊,因为那里存在瞬态事件。
作为说明,我们接下来将视网膜模型应用于一个黑暗视觉场景的网络摄像头视频流。在这个视觉场景中,在大学的一个圆形剧场中捕获,一些学生在与老师交谈时正在移动。
在此视频序列中,由于环境昏暗,信噪比低,并且由于低质量的图像捕获工具链,视觉特征边缘存在颜色伪影。
image
下方显示了应用于整个图像的视网膜中央凹视觉。在所使用的视网膜配置中,全局亮度得以保留,局部对比度得到增强。此外,信噪比也得到改善:由于高频时空噪声被降低,增强的细节不会被任何增强的噪声所破坏。
image
下方是视网膜模型大细胞输出的结果。其信号在瞬态事件发生的地方很强。在这里,一名学生在图像底部移动,因此产生了高能量。图像的其余部分是静态的,然而,它被强烈的噪声破坏。在这里,视网膜过滤掉了大部分噪声,从而产生了低的假运动区域“警报”。该通道可用作瞬态/移动区域检测器:它将为低成本的分割工具提供相关信息,该工具将突出显示事件发生的区域。
image
视网膜用例
该模型主要可用于时空视频特效,但也可用于以下目的:
- 进行纹理分析,具有增强的信噪比和对输入图像亮度范围鲁棒的增强细节(查看小细胞视网膜通道输出)
- 进行运动分析,同样受益于前面提到的特性。
参考文献
更多信息,请参考以下论文:[25]
- 请参阅 Jeanny Herault 的参考著作,您可以在他的书 [138] 中阅读。
该视网膜滤波器代码包含了博士/研究同事的研究贡献,作者从中重新编写了代码。
- 查看retinacolor.hpp模块,了解Brice Chaix de Lavarene博士的颜色镶嵌/解镶嵌及其参考论文[69]
- 查看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 (视网膜描述)库才能编译。
// compile
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<<"Program init error : "<<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[]) {
用于从视频文件、图像序列或摄像头捕获视频的类。
Definition videoio.hpp:772
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;
}
cv::getTickFrequency
double getTickFrequency()
现在,一切都已准备就绪,可以运行视网膜模型了。这里我建议分配一个视网膜实例并管理可能的对数采样选项。视网膜构造函数至少需要一个cv::Size对象,该对象显示将要管理的输入数据大小。可以激活其他选项,例如颜色及其相关的颜色复用策略(这里使用enum cv::bioinspired::RETINA_COLOR_BAYER选择拜耳复用)。如果使用对数采样,可以调整图像缩小因子(较小的输出图像)和对数采样强度。
if (useLogSampling)
{
}
else
myRetina = cv::bioinspired::createRetina(inputFrame.
size());
MatSize size
定义 mat.hpp:2187
@ RETINA_COLOR_BAYER
标准拜耳采样
定义 retina.hpp:86
std::shared_ptr< _Tp > Ptr
Definition 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;
// grab retina outputs
myRetina->getMagno(retinaOutput_magno);
// draw retina outputs
}
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
视网膜参数,如何设置?
首先,建议阅读参考论文[25]
完成此操作后,打开由演示生成的配置文件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之间重新缩放。请注意,在这种情况下,大细胞输出级别(运动/瞬态通道检测)以及残余噪声也将被重新缩放!
注意:使用颜色需要颜色通道的多路复用/解复用,这也会需要更多的处理。使用灰度图像可以预期更快的处理速度:所有视网膜处理大约每个像素需要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 指定执行局部适应的区域大小。低值导致短距离局部适应(对噪声更敏感),高值确保对数压缩。