2018年6月

掌握这些快捷命令以找出你正在运行的 Linux 系统的内核版本和发行版。

“什么版本的 Linux ?”这个问题可能意味着两个不同的东西。严格地说,Linux 是内核,所以问题可以特指内核的版本号,或者 “Linux” 可以更通俗地用来指整个发行版,就像在 Fedora Linux 或 Ubuntu Linux 中一样。

两者都很重要,你可能需要知道其中一个或全部答案来修复系统中的问题。例如,了解已安装的内核版本可能有助于诊断带有专有驱动程序的问题,并且确定正在运行的发行版将帮助你快速确定是否应该使用 aptdnfyum 或其他命令来安装软件包。

以下内容将帮助你了解 Linux 内核的版本和/或系统上正在运行的 Linux 发行版是什么。

如何找到 Linux 内核版本

要找出哪个 Linux 内核版本正在运行,运行以下命令:

uname -srm

或者,可以使用更长,更具描述性的各种标志的版本来运行该命令:

uname --kernel-name --kernel-release --machine

无论哪种方式,输出都应该类似于以下内容:

Linux 4.16.10-300.fc28.x86_64 x86_64

这为你提供了(按顺序):内核名称、内核版本以及运行内核的硬件类型。在上面的情况下,内核是 Linux ,版本 4.16.10-300.fc28.x8664 ,运行于 x8664 系统。

有关 uname 命令的更多信息可以通过运行 man uname 找到。

如何找出 Linux 发行版

有几种方法可以确定系统上运行的是哪个发行版,但最快的方法是检查 /etc/os-release 文件的内容。此文件提供有关发行版的信息,包括但不限于发行版名称及其版本号。某些发行版的 os-release 文件包含比其他发行版更多的细节,但任何包含 os-release 文件的发行版都应该提供发行版的名称和版本。

要查看 os-release 文件的内容,运行以下命令:

cat /etc/os-release

在 Fedora 28 中,输出如下所示:

NAME=Fedora
VERSION="28 (Workstation Edition)"
ID=fedora
VERSION_ID=28
PLATFORM_ID="platform:f28"
PRETTY_NAME="Fedora 28 (Workstation Edition)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:28"
HOME_URL="https://fedoraproject.org/"
SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=28
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=28
PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
VARIANT="Workstation Edition"
VARIANT_ID=workstation

如上面那个例子展示的那样,Fedora 的 os-release 文件提供了发行版的名称和版本,但它也标识这个安装的变体(“Workstation Edition”)。如果我们在 Fedora 28 服务器版本上运行相同的命令,os-release 文件的内容会反映在 VARIANTVARIANT_ID 行中。

有时候知道一个发行版是否与另一个发行版相似非常有用,因此 os-release 文件可以包含一个 ID_LIKE 行,用于标识正在运行的是基于什么的发行版或类似的发行版。例如,Red Hat Linux 企业版的 os-release 文件包含 ID_LIKE 行,声明 RHEL 与 Fedora 类似;CentOS 的 os-release 文件声明 CentOS 与 RHEL 和 Fedora 类似。如果你正在使用基于另一个发行版的发行版并需要查找解决问题的说明,那么 ID_LIKE 行非常有用。

CentOS 的 os-release 文件清楚地表明它就像 RHEL 一样,所以在各种论坛中关于 RHEL 的文档,问题和答案应该(大多数情况下)适用于 CentOS。CentOS 被设计成一个 RHEL 近亲,因此在某些字段它更兼容其 ID_LIKE 系统的字段。如果你找不到正在运行的发行版的信息,检查有关 “类似” 发行版的答案总是一个好主意。

有关 os-release 文件的更多信息可以通过运行 man os-release 命令来查找。


via: https://opensource.com/article/18/6/linux-version

作者:Joshua Allen Holm 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在上篇文章中我们提到了闭包、对象、以及栈外的其它东西。我们学习的大部分内容都是与特定编程语言无关的元素,但是,我主要还是专注于 JavaScript,以及一些 C。让我们以一个简单的 C 程序开始,它的功能是读取一首歌曲和乐队名字,然后将它们输出给用户:

#include <stdio.h>
#include <string.h>

char *read()
{    
    char data[64];
    fgets(data, 64, stdin);
    return data;
}

int main(int argc, char *argv[])
{
    char *song, *band;

    puts("Enter song, then band:");
    song = read();
    band = read();

    printf("\n%sby %s", song, band);
    return 0;
}

stackFolly.c 下载

如果你运行这个程序,你会得到什么?(=> 表示程序输出):

./stackFolly
=> Enter song, then band:
The Past is a Grotesque Animal
of Montreal

=> ?ǿontreal
=> by ?ǿontreal

(曾经的 C 新手说)发生了错误?

事实证明,函数的栈变量的内容仅在栈帧活动期间才是可用的,也就是说,仅在函数返回之前。在上面的返回中,被栈帧使用过的内存 被认为是可用的,并且在下一个函数调用中可以被覆写。

下面的图展示了这种情况下究竟发生了什么。这个图现在有一个图片映射(LCTT 译注:译文中无法包含此映射,上下两个矩形区域分别链接至输出的 #47 行和 #70 行),因此,你可以点击一个数据片断去看一下相关的 GDB 输出(GDB 命令在 这里)。只要 read() 读取了歌曲的名字,栈将是这个样子:

在这个时候,这个 song 变量立即指向到歌曲的名字。不幸的是,存储字符串的内存位置准备被下次调用的任意函数的栈帧重用。在这种情况下,read() 再次被调用,而且使用的是同一个位置的栈帧,因此,结果变成下图的样子(LCTT 译注:上下两个矩形映射分别链接至 #76 行和 #79 行):

乐队名字被读入到相同的内存位置,并且覆盖了前面存储的歌曲名字。bandsong 最终都准确指向到相同点。最后,我们甚至都不能得到 “of Montreal”(LCTT 译注:一个欧美乐队的名字) 的正确输出。你能猜到是为什么吗?

因此,即使栈很有用,但也有很重要的限制。它不能被一个函数用于去存储比该函数的运行周期还要长的数据。你必须将它交给 ,然后与热点缓存、明确的瞬时操作、以及频繁计算的偏移等内容道别。有利的一面是,它是工作 的:

这个代价是你必须记得去 free() 内存,或者由一个垃圾回收机制花费一些性能来随机回收,垃圾回收将去找到未使用的堆对象,然后去回收它们。那就是栈和堆之间在本质上的权衡:性能 vs. 灵活性。

大多数编程语言的虚拟机都有一个中间层用来做一个 C 程序员该做的一些事情。栈被用于值类型,比如,整数、浮点数、以及布尔型。这些都按特定值(像上面的 argc )的字节顺序被直接保存在本地变量和对象字段中。相比之下,堆被用于引用类型,比如,字符串和 对象。 变量和字段包含一个引用到这个对象的内存地址,像上面的 songband

参考这个 JavaScript 函数:

function fn()
{
    var a = 10;
    var b = { name: 'foo', n: 10 };
}

它可能的结果如下(LCTT 译注:图片内“object”、“string”和“a”的映射分别链接至 #1671 行、 #8656 行和 #1264 行):

我之所以说“可能”的原因是,特定的行为高度依赖于实现。这篇文章使用的许多流程图形是以一个 V8 为中心的方法,这些图形都链接到相关的源代码。在 V8 中,仅 小整数以值的方式保存。因此,从现在开始,我将在对象中直接以字符串去展示,以避免引起混乱,但是,请记住,正如上图所示的那样,它们在堆中是分开保存的。

现在,我们来看一下闭包,它其实很简单,但是由于我们将它宣传的过于夸张,以致于有点神化了。先看一个简单的 JS 函数:

function add(a, b)
{
    var c = a + b;
    return c;
}

这个函数定义了一个 词法域 lexical scope ,它是一个快乐的小王国,在这里它的名字 abc 是有明确意义的。它有两个参数和由函数声明的一个本地变量。程序也可以在别的地方使用相同的名字,但是在 add 内部它们所引用的内容是明确的。尽管词法域是一个很好的术语,它符合我们直观上的理解:毕竟,我们从字面意义上看,我们可以像词法分析器一样,把它看作在源代码中的一个文本块。

在看到栈帧的操作之后,很容易想像出这个名称的具体实现。在 add 内部,这些名字引用到函数的每个运行实例中私有的栈的位置。这种情况在一个虚拟机中经常发生。

现在,我们来嵌套两个词法域:

function makeGreeter()
{
    return function hi(name){
        console.log('hi, ' + name);
    }
}

var hi = makeGreeter();
hi('dear reader'); // prints "hi, dear reader"

那样更有趣。函数 hi 在函数 makeGreeter 运行的时候被构建在它内部。它有它自己的词法域,name 在这个地方是一个栈上的参数,但是,它似乎也可以访问父级的词法域,它可以那样做。我们来看一下那样做的好处:

function makeGreeter(greeting)
{
    return function greet(name){
        console.log(greeting + ', ' + name);
    }
}

var heya = makeGreeter('HEYA');
heya('dear reader'); // prints "HEYA, dear reader"

虽然有点不习惯,但是很酷。即便这样违背了我们的直觉:greeting 确实看起来像一个栈变量,这种类型应该在 makeGreeter() 返回后消失。可是因为 greet() 一直保持工作,出现了一些奇怪的事情。进入闭包(LCTT 译注:“Context” 和 “JSFunction” 映射分别链接至 #188 行和 #7245 行):

虚拟机分配一个对象去保存被里面的 greet() 使用的父级变量。它就好像是 makeGreeter 的词法作用域在那个时刻被 关闭 closed over 了,一旦需要时被具体化到一个堆对象(在这个案例中,是指返回的函数的生命周期)。因此叫做 闭包 closure ,当你这样去想它的时候,它的名字就有意义了。如果使用(或者捕获)了更多的父级变量,对象内容将有更多的属性,每个捕获的变量有一个。当然,发送到 greet() 的代码知道从对象内容中去读取问候语,而不是从栈上。

这是完整的示例:

function makeGreeter(greetings)
{
    var count = 0;
    var greeter = {};

    for (var i = 0; i < greetings.length; i++) {
        var greeting = greetings[i];

        greeter[greeting] = function(name){
            count++;
            console.log(greeting + ', ' + name);
        }
    }

    greeter.count = function(){return count;}

    return greeter;
}

var greeter = makeGreeter(["hi", "hello","howdy"])
greeter.hi('poppet');//prints "howdy, poppet"
greeter.hello('darling');// prints "howdy, darling"
greeter.count(); // returns 2

是的,count() 在工作,但是我们的 greeter 是在 howdy 中的栈上。你能告诉我为什么吗?我们使用 count 是一条线索:尽管词法域进入一个堆对象中被关闭,但是变量(或者对象属性)带的值仍然可能被改变。下图是我们拥有的内容(LCTT 译注:映射从左到右“Object”、“JSFunction”和“Context”分别链接至 #1671 行、#7245 行和 #188 行):

这是一个被所有函数共享的公共内容。那就是为什么 count 工作的原因。但是,greeting 也是被共享的,并且它被设置为迭代结束后的最后一个值,在这个案例中是 “howdy”。这是一个很常见的一般错误,避免它的简单方法是,引用一个函数调用,以闭包变量作为一个参数。在 CoffeeScript 中, do 命令提供了一个实现这种目的的简单方式。下面是对我们的 greeter 的一个简单的解决方案:

function makeGreeter(greetings)
{
    var count = 0;
    var greeter = {};

    greetings.forEach(function(greeting){
        greeter[greeting] = function(name){
            count++;
            console.log(greeting + ', ' + name);
        }
    });

    greeter.count = function(){return count;}

    return greeter;
}

var greeter = makeGreeter(["hi", "hello", "howdy"])
greeter.hi('poppet'); // prints "hi, poppet"
greeter.hello('darling'); // prints "hello, darling"
greeter.count(); // returns 2

它现在是工作的,并且结果将变成下图所示(LCTT 译注:映射从左到右“Object”、“JSFunction”和“Context”分别链接至 #1671 行、#7245 行和 #188 行):

这里有许多箭头!在这里我们感兴趣的特性是:在我们的代码中,我们闭包了两个嵌套的词法内容,并且完全可以确保我们得到了两个链接到堆上的对象内容。你可以嵌套并且闭包任何词法内容,像“俄罗斯套娃”似的,并且最终从本质上说你使用的是所有那些 Context 对象的一个链表。

当然,就像受信鸽携带信息启发实现了 TCP 一样,去实现这些编程语言的特性也有很多种方法。例如,ES6 规范定义了 词法环境 作为 环境记录( 大致相当于在一个块内的本地标识)的组成部分,加上一个链接到外部环境的记录,这样就允许我们看到的嵌套。逻辑规则是由规范(一个希望)所确定的,但是其实现取决于将它们变成比特和字节的转换。

你也可以检查具体案例中由 V8 产生的汇编代码。Vyacheslav Egorov 有一篇很好的文章,它使用 V8 的 闭包内部构件 详细解释了这一过程。我刚开始学习 V8,因此,欢迎指教。如果你熟悉 C#,检查闭包产生的中间代码将会很受启发 —— 你将看到显式定义的 V8 内容和实例化的模拟。

闭包是个强大的“家伙”。它在被一组函数共享期间,提供了一个简单的方式去隐藏来自调用者的信息。我喜欢它们真正地隐藏你的数据:不像对象字段,调用者并不能访问或者甚至是看到闭包变量。保持接口清晰而安全。

但是,它们并不是“银弹”(LCTT 译注:意指极为有效的解决方案,或者寄予厚望的新技术)。有时候一个对象的拥护者和一个闭包的狂热者会无休止地争论它们的优点。就像大多数的技术讨论一样,他们通常更关注的是自尊而不是真正的权衡。不管怎样,Anton van Straaten 的这篇 史诗级的公案 解决了这个问题:

德高望重的老师 Qc Na 和它的学生 Anton 一起散步。Anton 希望将老师引入到一个讨论中,Anton 说:“老师,我听说对象是一个非常好的东西,是这样的吗?Qc Na 同情地看了一眼,责备它的学生说:“可怜的孩子 —— 对象不过是穷人的闭包而已。” Anton 待它的老师走了之后,回到他的房间,专心学习闭包。他认真地阅读了完整的 “Lambda:The Ultimate…" 系列文章和它的相关资料,并使用一个基于闭包的对象系统实现了一个小的架构解释器。他学到了很多的东西,并期待告诉老师他的进步。在又一次和 Qc Na 散步时,Anton 尝试给老师留下一个好的印象,说“老师,我仔细研究了这个问题,并且,现在理解了对象真的是穷人的闭包。”Qc Na 用它的手杖打了一下 Anton 说:“你什么时候才能明白?闭包是穷人的对象。”在那个时候,Anton 顿悟了。Anton van Straaten 说:“原来架构这么酷啊?”

探秘“栈”系列文章到此结束了。后面我将计划去写一些其它的编程语言实现的主题,像对象绑定和虚表。但是,内核调用是很强大的,因此,明天将发布一篇操作系统的文章。我邀请你 订阅关注我


via:https://manybutfinite.com/post/closures-objects-heap/

作者:Gustavo Duarte 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

想寻找一个稳定、安全的基础来为您的企业应用程序的未来提供动力?Linux 服务器可能是答案。

IT 组织力求通过提高生产力和提供更快速的服务来提供商业价值,同时保持足够的灵活性,将云、容器和配置自动化等创新融入其中。现代的工作任务,无论是裸机、虚拟机、容器,还是私有云或公共云,都需要是可移植且可扩展的。支持所有的这些需要一个现代且安全的平台。

通往创新最直接的途径并不总是一条直线。随着私有云和公共云、多种体系架构和虚拟化的日益普及,当今的数据中心就像一个球一样,基础设施的选择各不相同,从而带来了维度和深度。就像飞行员依赖空中交通管制员提供持续更新一样,数字化转型之旅应该由像 Linux 这样可信赖的操作系统来指引,以提供持续更新的技术,以及对云、容器和配置自动化等创新的最有效和安全的访问。

Linux 是一个家族,它围绕 Linux 内核构建的自由、开源软件操作系统。最初开发的是基于 Intel x86 架构的个人电脑,此后比起任何其他操作系统,Linux 被移植到更多的平台上。得益于基于 Linux 内核的 Android 操作系统在智能手机上的主导地位,Linux 拥有所有通用操作系统中最大的安装基数。Linux 也是服务器和大型计算机等“大型机”系统的主要操作系统,也是 TOP500 超级计算机上唯一使用的操作系统。

为了利用这一功能,许多企业公司已经采用具有高性能的 Linux 开源操作系统的服务器。这些旨在处理最苛刻的业务应用程序要求,如网络和系统管理、数据库管理和 Web 服务。出于其稳定性、安全性和灵活性,通常选择 Linux 服务器而不是其他服务器操作系统。位居前列的 Linux 服务器操作系统包括 DebianUbuntu ServerCentOSSlackwareGentoo

在企业级工作任务中,你应该考虑企业级 Linux 服务器上的哪些功能和优势?首先,通过对 Linux 和 Windows 管理员都熟悉的界面,内置的安全控制和可管理的扩展使你可以专注于业务增长,而不是对安全漏洞和昂贵的管理配置错误担心。你选择的 Linux 服务器应提供安全技术和认证,并保持增强以抵御入侵,保护你的数据,而且满足一个开放源代码项目或特定系统供应商的合规性。它应该:

  • 使用集中式身份管理和安全增强型 Linux(SELinux)、强制访问控制(MAC)等集成控制功能来安全地交付资源,这是通用标准认证FIPS 140-2 认证,并且第一个 Linux 容器框架支持也是通用标准认证。
  • 自动执行法规遵从和安全配置修复 应贯穿于你的系统和容器。通过像 OpenSCAP 的映像扫描,它应该可以检查、补救漏洞和配置安全基准,包括针对 PCI-DSSDISA STIG 等的国家清单程序内容。另外,它应该在整个混合环境中集中和扩展配置修复。
  • 持续接收漏洞安全更新,从上游社区或特定的系统供应商,如有可能,可在下一工作日补救并提供所有关键问题,以最大限度地降低业务影响。

作为混合数据中心的基础,Linux 服务器应提供平台可管理性和与传统管理和自动化基础设施的灵活集成。与非付费的 Linux 基础设施相比,这将节省 IT 员工的时间并减少意外停机的情况。它应该:

  • 通过内置功能 加速整个数据中心的映像构建、部署和补丁管理,并丰富系统生命周期管理,配置和增强的修补等等。
  • 通过一个 简单易用的 web 界面管理单个系统,包括存储、网络、容器、服务等等。
  • 通过使用 AnsibleChefSaltPuppet 等原生配置管理工具,可以跨多个异构环境实现 自动化一致性和合规性,并通过系统角色减少脚本返工。
  • 通过就地升级 简化平台更新,消除机器迁移和应用程序重建的麻烦。
  • 通过使用预测分析工具自动识别和修复异常情况及其根本原因,在技术问题影响业务运营之前 解决技术问题

Linux 服务器正在在全球范围内推动创新。作为一个企业工作任务的平台,Linux 服务器应该为运行当下和未来业务的应用程序提供稳定,安全和性能驱动的基础。


via: https://opensource.com/article/18/5/what-linux-server

作者:Daniel Oh 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

通过从命令行运行它,得到这个易于使用的 Linux 存储工具的主要用途。

正如本系列的第一部分第二部分中所讨论的,Stratis 是一个具有与 ZFSBtrfs 相似功能的卷管理文件系统。在本文中,我们将介绍如何在命令行上使用 Stratis。

安装 Stratis

对于非开发人员,现在尝试 Stratis 最简单的方法是在 Fedora 28 中。

你可以用以下命令安装 Stratis 守护进程和 Stratis 命令行工具:

# dnf install stratis-cli stratisd

创建一个池

Stratis 有三个概念:blockdevs、池和文件系统。 Blockdevs 是组成池的块设备,例如磁盘或磁盘分区。一旦创建池,就可以从中创建文件系统。

假设你的系统上有一个名为 vdg 的块设备,它目前没有被使用或挂载,你可以在它上面创建一个 Stratis 池:

# stratis pool create mypool /dev/vdg

这假设 vdg 是完全清零并且是空的。如果它没有被使用,但有旧数据,则可能需要使用 pool create-force 选项。如果正在使用,请勿将它用于 Stratis。

如果你想从多个块设备创建一个池,只需在 pool create 命令行中列出它们。你也可以稍后使用 blockdev add-data 命令添加更多的 blockdevs。请注意,Stratis 要求 blockdevs 的大小至少为 1 GiB。

创建文件系统

在你创建了一个名为 mypool 的池后,你可以从它创建文件系统:

# stratis fs create mypool myfs1

mypool 池创建一个名为 myfs1 的文件系统后,可以使用 Stratis 在 /dev/stratis 中创建的条目来挂载并使用它:

# mkdir myfs1
# mount /dev/stratis/mypool/myfs1 myfs1

文件系统现在已被挂载在 myfs1 上并准备可以使用。

快照

除了创建空文件系统之外,你还可以创建一个文件系统作为现有文件系统的快照:

# stratis fs snapshot mypool myfs1 myfs1-experiment

这样做后,你可以挂载新的 myfs1-experiment,它将初始包含与 myfs1 相同的文件内容,但它可能随着文件系统的修改而改变。无论你对 myfs1-experiment 所做的任何更改都不会反映到 myfs1 中,除非你卸载了 myfs1 并将其销毁:

# umount myfs1
# stratis fs destroy mypool myfs1

然后进行快照以重新创建并重新挂载它:

# stratis fs snapshot mypool myfs1-experiment myfs1
# mount /dev/stratis/mypool/myfs1 myfs1

获取信息

Stratis 可以列出系统中的池:

# stratis pool list

随着文件系统写入更多数据,你将看到 “Total Physical Used” 值的增加。当这个值接近 “Total Physical Size” 时要小心。我们仍在努力处理这个问题。

列出池中的文件系统:

# stratis fs list mypool

列出组成池的 blockdevs:

# stratis blockdev list mypool

目前只提供这些最少的信息,但它们将在未来提供更多信息。

摧毁池

当你了解了 Stratis 可以做什么后,要摧毁池,首先确保从它创建的所有文件系统都被卸载并销毁,然后使用 pool destroy 命令:

# umount myfs1
# umount myfs1-experiment (if you created it)
# stratis fs destroy mypool myfs1
# stratis fs destroy mypool myfs1-experiment
# stratis pool destroy mypool

stratis pool list 现在应该显示没有池。

就是这些!有关更多信息,请参阅手册页:“man stratis”。


via: https://opensource.com/article/18/5/stratis-storage-linux-command-line

作者:Andy Grover 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本教程中,我们将学习如何使用由不同曝光设置拍摄的多张图像创建 高动态范围 High Dynamic Range (HDR)图像。 我们将以 C++ 和 Python 两种形式分享代码。

什么是高动态范围成像?

大多数数码相机和显示器都是按照 24 位矩阵捕获或者显示彩色图像。 每个颜色通道有 8 位,因此每个通道的像素值在 0-255 范围内。 换句话说,普通的相机或者显示器的动态范围是有限的。

但是,我们周围世界动态范围极大。 在车库内关灯就会变黑,直接看着太阳就会变得非常亮。 即使不考虑这些极端,在日常情况下,8 位的通道勉强可以捕捉到现场场景。 因此,相机会尝试去评估光照并且自动设置曝光,这样图像的最关注区域就会有良好的动态范围,并且太暗和太亮的部分会被相应截取为 0 和 255。

在下图中,左侧的图像是正常曝光的图像。 请注意,由于相机决定使用拍摄主体(我的儿子)的设置,所以背景中的天空已经完全流失了,但是明亮的天空也因此被刷掉了。 右侧的图像是由 iPhone 生成的HDR图像。

High Dynamic Range (HDR)

iPhone 是如何拍摄 HDR 图像的呢? 它实际上采用三种不同的曝光度拍摄了 3 张图像,3 张图像拍摄非常迅速,在 3 张图像之间几乎没有产生位移。然后组合三幅图像来产生 HDR 图像。 我们将在下一节看到一些细节。

将在不同曝光设置下获取的相同场景的不同图像组合的过程称为高动态范围(HDR)成像。

高动态范围(HDR)成像是如何工作的?

在本节中,我们来看下使用 OpenCV 创建 HDR 图像的步骤。

要想轻松学习本教程,请点击此处下载 C++ 和 Python 代码还有图像。 如果您有兴趣了解更多关于人工智能,计算机视觉和机器学习的信息,请订阅我们的电子杂志。

第 1 步:捕获不同曝光度的多张图像

当我们使用相机拍照时,每个通道只有 8 位来表示场景的动态范围(亮度范围)。 但是,通过改变快门速度,我们可以在不同的曝光条件下拍摄多个场景图像。 大多数单反相机(SLR)有一个功能称为 自动包围式曝光 Auto Exposure Bracketing (AEB),只需按一下按钮,我们就可以在不同的曝光下拍摄多张照片。 如果你正在使用 iPhone,你可以使用这个自动包围式 HDR 应用程序,如果你是一个 Android 用户,你可以尝试一个更好的相机应用程序

场景没有变化时,在相机上使用自动包围式曝光或在手机上使用自动包围式应用程序,我们可以一张接一张地快速拍摄多张照片。 当我们在 iPhone 中使用 HDR 模式时,会拍摄三张照片。

  1. 曝光不足的图像:该图像比正确曝光的图像更暗。 目标是捕捉非常明亮的图像部分。
  2. 正确曝光的图像:这是相机将根据其估计的照明拍摄的常规图像。
  3. 曝光过度的图像:该图像比正确曝光的图像更亮。 目标是拍摄非常黑暗的图像部分。

但是,如果场景的动态范围很大,我们可以拍摄三张以上的图片来合成 HDR 图像。 在本教程中,我们将使用曝光时间为1/30 秒,0.25 秒,2.5 秒和 15 秒的 4 张图像。 缩略图如下所示。

Auto Exposure Bracketed  HDR image sequence

单反相机或手机的曝光时间和其他设置的信息通常存储在 JPEG 文件的 EXIF 元数据中。 查看此链接可在 Windows 和 Mac 中查看存储在 JPEG 文件中的 EXIF 元数据。 或者,您可以使用我最喜欢的名为 EXIFTOOL 的查看 EXIF 的命令行工具。

我们先从读取分配到不同曝光时间的图像开始。

C++

void readImagesAndTimes(vector<Mat> &images, vector<float> &times)
{

  int numImages = 4;

  // 曝光时间列表
  static const float timesArray[] = {1/30.0f,0.25,2.5,15.0};
  times.assign(timesArray, timesArray + numImages);

  // 图像文件名称列表
  static const char* filenames[] = {"img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"};
  for(int i=0; i < numImages; i++)
  {
    Mat im = imread(filenames[i]);
    images.push_back(im);
  }

}

Python

def readImagesAndTimes():
  # 曝光时间列表
  times = np.array([ 1/30.0, 0.25, 2.5, 15.0 ], dtype=np.float32)

  # 图像文件名称列表
  filenames = ["img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"]
  images = []
  for filename in filenames:
    im = cv2.imread(filename)
    images.append(im)

  return images, times

第 2 步:对齐图像

合成 HDR 图像时使用的图像如果未对齐可能会导致严重的伪影。 在下图中,左侧的图像是使用未对齐的图像组成的 HDR 图像,右侧的图像是使用对齐的图像的图像。 通过放大图像的一部分(使用红色圆圈显示的)我们会在左侧图像中看到严重的鬼影。

Misalignment problem in HDR

在拍摄照片制作 HDR 图像时,专业摄影师自然是将相机安装在三脚架上。 他们还使用称为镜像锁定功能来减少额外的振动。 即使如此,图像可能仍然没有完美对齐,因为没有办法保证无振动的环境。 使用手持相机或手机拍摄图像时,对齐问题会变得更糟。

幸运的是,OpenCV 提供了一种简单的方法,使用 AlignMTB 对齐这些图像。 该算法将所有图像转换为 中值阈值位图 median threshold bitmaps (MTB)。 图像的 MTB 生成方式为将比中值亮度的更亮的分配为 1,其余为 0。 MTB 不随曝光时间的改变而改变。 因此不需要我们指定曝光时间就可以对齐 MTB。

基于 MTB 的对齐方式的代码如下。

C++

// 对齐输入图像
Ptr<AlignMTB> alignMTB = createAlignMTB();
alignMTB->process(images, images);

Python

# 对齐输入图像
alignMTB = cv2.createAlignMTB()
alignMTB.process(images, images)

第 3 步:提取相机响应函数

典型相机的响应与场景亮度不成线性关系。 那是什么意思呢? 假设有两个物体由同一个相机拍摄,在现实世界中其中一个物体是另一个物体亮度的两倍。 当您测量照片中两个物体的像素亮度时,较亮物体的像素值将不会是较暗物体的两倍。 在不估计 相机响应函数 Camera Response Function (CRF)的情况下,我们将无法将图像合并到一个HDR图像中。

将多个曝光图像合并为 HDR 图像意味着什么?

只考虑图像的某个位置 (x,y) 一个像素。 如果 CRF 是线性的,则像素值将直接与曝光时间成比例,除非像素在特定图像中太暗(即接近 0)或太亮(即接近 255)。 我们可以过滤出这些不好的像素(太暗或太亮),并且将像素值除以曝光时间来估计像素的亮度,然后在像素不差的(太暗或太亮)所有图像上对亮度值取平均。我们可以对所有像素进行这样的处理,并通过对“好”像素进行平均来获得所有像素的单张图像。

但是 CRF 不是线性的, 我们需要评估 CRF 把图像强度变成线性,然后才能合并或者平均它们。

好消息是,如果我们知道每个图像的曝光时间,则可以从图像估计 CRF。 与计算机视觉中的许多问题一样,找到 CRF 的问题本质是一个最优解问题,其目标是使由数据项和平滑项组成的目标函数最小化。 这些问题通常会降维到线性最小二乘问题,这些问题可以使用 奇异值分解 Singular Value Decomposition (SVD)来解决,奇异值分解是所有线性代数包的一部分。 CRF 提取算法的细节在从照片提取高动态范围辐射图这篇论文中可以找到。

使用 OpenCV 的 CalibrateDebevec 或者 CalibrateRobertson 就可以用 2 行代码找到 CRF。本篇教程中我们使用 CalibrateDebevec

C++

// 获取图像响应函数 (CRF)
Mat responseDebevec;
Ptr<CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
calibrateDebevec->process(images, responseDebevec, times);

Python

# 获取图像响应函数 (CRF)
calibrateDebevec = cv2.createCalibrateDebevec()
responseDebevec = calibrateDebevec.process(images, times)

下图显示了使用红绿蓝通道的图像提取的 CRF。

Camera Response Function

第 4 步:合并图像

一旦 CRF 评估结束,我们可以使用 MergeDebevec 将曝光图像合并成一个HDR图像。 C++ 和 Python 代码如下所示。

C++

// 将图像合并为HDR线性图像
Mat hdrDebevec;
Ptr<MergeDebevec> mergeDebevec = createMergeDebevec();
mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
// 保存图像
imwrite("hdrDebevec.hdr", hdrDebevec);

Python

# 将图像合并为HDR线性图像
mergeDebevec = cv2.createMergeDebevec()
hdrDebevec = mergeDebevec.process(images, times, responseDebevec)
# 保存图像
cv2.imwrite("hdrDebevec.hdr", hdrDebevec)

上面保存的 HDR 图像可以在 Photoshop 中加载并进行色调映射。示例图像如下所示。

HDR Photoshop tone mapping

HDR Photoshop 色调映射

第 5 步:色调映射

现在我们已经将我们的曝光图像合并到一个 HDR 图像中。 你能猜出这个图像的最小和最大像素值吗? 对于黑色条件,最小值显然为 0。 理论最大值是什么? 无限大! 在实践中,不同情况下的最大值是不同的。 如果场景包含非常明亮的光源,那么最大值就会非常大。

尽管我们已经使用多个图像恢复了相对亮度信息,但是我们现在又面临了新的挑战:将这些信息保存为 24 位图像用于显示。

将高动态范围(HDR)图像转换为 8 位单通道图像的过程称为色调映射。这个过程的同时还需要保留尽可能多的细节。

有几种色调映射算法。 OpenCV 实现了其中的四个。 要记住的是没有一个绝对正确的方法来做色调映射。 通常,我们希望在色调映射图像中看到比任何一个曝光图像更多的细节。 有时色调映射的目标是产生逼真的图像,而且往往是产生超现实图像的目标。 在 OpenCV 中实现的算法倾向于产生现实的并不那么生动的结果。

我们来看看各种选项。 以下列出了不同色调映射算法的一些常见参数。

  1. 伽马 gamma :该参数通过应用伽马校正来压缩动态范围。 当伽马等于 1 时,不应用修正。 小于 1 的伽玛会使图像变暗,而大于 1 的伽马会使图像变亮。
  2. 饱和度 saturation :该参数用于增加或减少饱和度。 饱和度高时,色彩更丰富,更浓。 饱和度值接近零,使颜色逐渐消失为灰度。
  3. 对比度 contrast :控制输出图像的对比度(即 log(maxPixelValue/minPixelValue))。

让我们来探索 OpenCV 中可用的四种色调映射算法。

Drago 色调映射

Drago 色调映射的参数如下所示:

createTonemapDrago
(
float   gamma = 1.0f,
float   saturation = 1.0f,
float   bias = 0.85f 
)   

这里,bias[0, 1] 范围内偏差函数的值。 从 0.7 到 0.9 的值通常效果较好。 默认值是 0.85。 有关更多技术细节,请参阅这篇论文

C++ 和 Python 代码如下所示。 参数是通过反复试验获得的。 最后的结果乘以 3 只是因为它给出了最令人满意的结果。

C++

// 使用Drago色调映射算法获得24位彩色图像
Mat ldrDrago;
Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.0, 0.7);
tonemapDrago->process(hdrDebevec, ldrDrago);
ldrDrago = 3 * ldrDrago;
imwrite("ldr-Drago.jpg", ldrDrago * 255);

Python

# 使用Drago色调映射算法获得24位彩色图像
tonemapDrago = cv2.createTonemapDrago(1.0, 0.7)
ldrDrago = tonemapDrago.process(hdrDebevec)
ldrDrago = 3 * ldrDrago
cv2.imwrite("ldr-Drago.jpg", ldrDrago * 255)

结果如下:

HDR tone mapping using Drago's algorithm

使用Drago算法的HDR色调映射

Durand 色调映射

Durand 色调映射的参数如下所示:

createTonemapDurand 
(   
  float     gamma = 1.0f, 
  float     contrast = 4.0f,
  float     saturation = 1.0f,
  float     sigma_space = 2.0f,
  float     sigma_color = 2.0f 
); 

该算法基于将图像分解为基础层和细节层。 使用称为双边滤波器的边缘保留滤波器来获得基本层。 sigma_spacesigma_color 是双边滤波器的参数,分别控制空间域和彩色域中的平滑量。

有关更多详细信息,请查看这篇论文

C++

// 使用Durand色调映射算法获得24位彩色图像
Mat ldrDurand;
Ptr<TonemapDurand> tonemapDurand = createTonemapDurand(1.5,4,1.0,1,1);
tonemapDurand->process(hdrDebevec, ldrDurand);
ldrDurand = 3 * ldrDurand;
imwrite("ldr-Durand.jpg", ldrDurand * 255);

Python

# 使用Durand色调映射算法获得24位彩色图像
 tonemapDurand = cv2.createTonemapDurand(1.5,4,1.0,1,1)
 ldrDurand = tonemapDurand.process(hdrDebevec)
 ldrDurand = 3 * ldrDurand
 cv2.imwrite("ldr-Durand.jpg", ldrDurand * 255)

结果如下:

HDR tone mapping using Durand's algorithm

使用Durand算法的HDR色调映射

Reinhard 色调映射


createTonemapReinhard
(
float   gamma = 1.0f,
float   intensity = 0.0f,
float   light_adapt = 1.0f,
float   color_adapt = 0.0f 
)   

intensity 参数应在 [-8, 8] 范围内。 更高的亮度值会产生更明亮的结果。 light_adapt 控制灯光,范围为 [0, 1]。 值 1 表示仅基于像素值的自适应,而值 0 表示全局自适应。 中间值可以用于两者的加权组合。 参数 color_adapt 控制色彩,范围为 [0, 1]。 如果值被设置为 1,则通道被独立处理,如果该值被设置为 0,则每个通道的适应级别相同。中间值可以用于两者的加权组合。

有关更多详细信息,请查看这篇论文

C++

// 使用Reinhard色调映射算法获得24位彩色图像
Mat ldrReinhard;
Ptr<TonemapReinhard> tonemapReinhard = createTonemapReinhard(1.5, 0,0,0);
tonemapReinhard->process(hdrDebevec, ldrReinhard);
imwrite("ldr-Reinhard.jpg", ldrReinhard * 255);

Python

# 使用Reinhard色调映射算法获得24位彩色图像
tonemapReinhard = cv2.createTonemapReinhard(1.5, 0,0,0)
ldrReinhard = tonemapReinhard.process(hdrDebevec)
cv2.imwrite("ldr-Reinhard.jpg", ldrReinhard * 255)

结果如下:

HDR tone mapping using Reinhard's algorithm

使用Reinhard算法的HDR色调映射

Mantiuk 色调映射

createTonemapMantiuk
(   
float   gamma = 1.0f,
float   scale = 0.7f,
float   saturation = 1.0f 
)   

参数 scale 是对比度比例因子。 从 0.7 到 0.9 的值通常效果较好

有关更多详细信息,请查看这篇论文

C++

// 使用Mantiuk色调映射算法获得24位彩色图像
Mat ldrMantiuk;
Ptr<TonemapMantiuk> tonemapMantiuk = createTonemapMantiuk(2.2,0.85, 1.2);
tonemapMantiuk->process(hdrDebevec, ldrMantiuk);
ldrMantiuk = 3 * ldrMantiuk;
imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255);

Python

# 使用Mantiuk色调映射算法获得24位彩色图像
tonemapMantiuk = cv2.createTonemapMantiuk(2.2,0.85, 1.2)
ldrMantiuk = tonemapMantiuk.process(hdrDebevec)
ldrMantiuk = 3 * ldrMantiuk
cv2.imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255)

结果如下:

HDR tone mapping using Mantiuk's algorithm

使用Mantiuk算法的HDR色调映射

订阅然后下载代码

如果你喜欢这篇文章,并希望下载本文中使用的代码(C++ 和 Python)和示例图片,请订阅我们的电子杂志。 您还将获得免费的计算机视觉资源指南。 在我们的电子杂志中,我们分享了用 C++ 还有 Python 编写的 OpenCV 教程和例子,以及计算机视觉和机器学习的算法和新闻。

点此订阅

图片致谢

本文中使用的四个曝光图像获得 CC BY-SA 3.0 许可,并从维基百科的 HDR 页面下载。 图像由 Kevin McCoy拍摄。


作者简介:

我是一位热爱计算机视觉和机器学习的企业家,拥有十多年的实践经验(还有博士学位)。

2007 年,在完成博士学位之后,我和我的顾问 David Kriegman 博士还有 Kevin Barnes 共同创办了 TAAZ 公司。 我们的计算机视觉和机器学习算法的可扩展性和鲁棒性已经经过了试用了我们产品的超过 1 亿的用户的严格测试。


via: http://www.learnopencv.com/high-dynamic-range-hdr-imaging-using-opencv-cpp-python/

作者:SATYA MALLICK 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

出于某种原因,你可能需要在将软件包安装到你的 Linux 系统之前对其进行测试。如果是这样,你很幸运!今天,我将向你展示如何在 Linux 中使用 Nix 包管理器来实现。Nix 包管理器的一个显著特性是它允许用户测试软件包而无需先安装它们。当你想要临时使用特定的程序时,这会很有帮助。

测试一个软件包而不在 Linux 中安装它

确保你先安装了 Nix 包管理器。如果尚未安装,请参阅以下指南。

例如,假设你想测试你的 C++ 代码。你不必安装 GCC。只需运行以下命令:

$ nix-shell -p gcc

该命令会构建或下载 gcc 软件包及其依赖项,然后将其放入一个存在 gcc 命令的 Bash shell 中,所有这些都不会影响正常环境。

LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
download-using-manifests.pl: perl: warning: Setting locale failed.
download-using-manifests.pl: perl: warning: Please check that your locale settings:
download-using-manifests.pl: LANGUAGE = (unset),
download-using-manifests.pl: LC_ALL = (unset),
download-using-manifests.pl: LANG = "en_US.UTF-8"
download-using-manifests.pl: are supported and installed on your system.
download-using-manifests.pl: perl: warning: Falling back to the standard locale ("C").
download-from-binary-cache.pl: perl: warning: Setting locale failed.
download-from-binary-cache.pl: perl: warning: Please check that your locale settings:
download-from-binary-cache.pl: LANGUAGE = (unset),
download-from-binary-cache.pl: LC_ALL = (unset),
download-from-binary-cache.pl: LANG = "en_US.UTF-8"

[...]

fetching path ‘/nix/store/6mk1s81va81dl4jfbhww86cwkl4gyf4j-stdenv’...
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

*** Downloading ‘https://cache.nixos.org/nar/0aznfg1g17a8jdzvnp3pqszs9rq2wiwf2rcgczyg5b3k6d0iricl.nar.xz’ to ‘/nix/store/6mk1s81va81dl4jfbhww86cwkl4gyf4j-stdenv’...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8324 100 8324 0 0 6353 0 0:00:01 0:00:01 --:--:-- 6373

[nix-shell:~]$

检查GCC版本:

[nix-shell:~]$ gcc -v
Using built-in specs.
COLLECT_GCC=/nix/store/dyj2k6ch35r1ips4vr97md2i0yvl4r5c-gcc-5.4.0/bin/gcc
COLLECT_LTO_WRAPPER=/nix/store/dyj2k6ch35r1ips4vr97md2i0yvl4r5c-gcc-5.4.0/libexec/gcc/x86_64-unknown-linux-gnu/5.4.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with:
Thread model: posix
gcc version 5.4.0 (GCC)

现在,继续并测试代码。完成后,输入 exit 返回到控制台。

[nix-shell:~]$ exit
exit

一旦你从 nix-shell 中退出,你就不能使用 GCC。

这是另一个例子。

$ nix-shell -p hello

这会构建或下载 GNU Hello 和它的依赖关系,然后将其放入 hello 命令所在的 Bash shell 中,所有这些都不会影响你的正常环境:

[nix-shell:~]$ hello
Hello, world!

输入 exit 返回到控制台。

[nix-shell:~]$ exit

现在测试你的 hello 程序是否可用。

$ hello
hello: command not found

有关 Nix 包管理器的更多详细信息,请参阅以下指南。

希望本篇对你有帮助!还会有更好的东西。敬请关注!!

干杯!


via: https://www.ostechnix.com/how-to-test-a-package-without-installing-it-in-linux/

作者:SK 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出