国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV计算图像

 二维码 16
Python      OpenCV


准备

我们还将使用rawpy和exifread Python包来读取RAW图像和图像元数据。 有关需求的完整列表如下。

numpy==1.18.1
scipy==1.4.1
matplotlib==3.1.2
requests==2.22.0
opencv-contrib-python==4.2.0.32
opencv-python==4.2.0.32
rawpy==0.13.1
ExifRead==2.1.2
wheels/wxPython-4.0.7.post2-cp38-cp38-linux_x86_64.whl

构思App

我们将使用OpenCV实施以下脚本,以便您可以在需要进行照片处理时使用它们:

  • gamma_correct.py::这是一个将伽玛校正应用于输入图像并显示结果图像的脚本。
  • hdr.py:这是一个将图像作为输入并生成高动态范围(HDR)图像作为输出的脚本。
  • panorama.py::这是一个脚本,它将多个图像作为输入并生成比单个图像大的单个拼接图像。

了解8位问题

我们惯用的典型联合图像专家组(JPEG)图像通过将每个像素编码为24位来工作,每个RGB(红色,绿色,蓝色)颜色分量一个8位数字,这使我们得到一个0内的整数 -255范围。 这只是一个数字255,但这是否足够的信息? 为了理解这一点,让我们尝试了解如何记录这些数字以及这些数字的含义。

当前大多数数码相机都使用按照相同原理工作的拜耳滤波器或等效滤波器。 拜耳过滤器是一组放置在网格上的不同颜色的传感器,类似于下图:

在上图中,每个传感器都测量进入其中的光的强度,四个传感器组成的一组代表一个像素。 来自这四个传感器的数据被合并以为我们提供R,G和B的三个值。

不同的相机可能在红色,绿色和蓝色像素的布局上略有不同,但是到了最后,它们使用的是小型传感器,可以将它们获得的辐射量离散化为0-255范围内的单个值。 0表示完全没有辐射,255表示传感器可以记录的最亮的辐射。

可检测到的亮度范围称为动态范围或亮度范围。 可以记录的最小辐射量(即1)与最大辐射量(即255)之间的比率称为对比度。

正如我们所说,JPEG文件的对比度为255:1。 当前大多数LCD监视器已经超过了该值,并且对比度高达1,000:1。 我敢打赌,你在等你的眼睛比例。 我不确定您的身份,但大多数人最多可以看到15,000:1。

因此,我们可以看到的甚至比我们最好的监视器所能显示的要多得多,而不仅仅是一个简单的JPEG文件存储。 不要太失望,因为最新的数码相机已经赶上了,现在可以捕捉高达28,000:1的强度比。

较小的动态范围是当您在拍摄照片且背景为阳光时看到的阳光和周围的环境都是白色而没有任何细节,或者前景中的一切都非常暗的原因。 这是一个示例屏幕截图:

因此,问题在于我们要么显示太亮的东西,要么显示太暗的东西。

了解RAW图像

原始文件通常比JPEG文件捕获更多的信息(通常每像素更多位),并且如果您要进行大量的后处理,则使用这些文件会更加方便,因为它们会产生更高质量最终图像。

因此,让我们看一下如何使用Python打开CR2文件并将其加载到OpenCV中。 为此,我们将使用一个名为rawpy的Python库。 为了方便起见,我们将编写一个名为load_image的函数,该函数可以处理RAW图像和常规JPEG文件,因此我们可以将这一部分抽象化:

  1. 首先,我们要注意导入:
    import rawpy
    import cv2
  1. 我们定义函数,并添加一个可选的bps参数,这将使我们能够控制图像的精度,也就是说,我们要检查是否需要完整的16位或仅够8位:
    def load_image(path, bps=16):
  1. 然后,如果文件扩展名为.CR2,我们将使用rawpy打开文件并提取图像而无需尝试进行任何后处理,因为我们想使用OpenCV来做到这一点:
    if path.suffix == '.CR2':
       with rawpy.imread(str(path)) as raw:
        data = raw.postprocess(no_auto_bright=True,gamma=(1, 1),output_bps=bps)
  1. 由于佳能(光学产品公司Canon Inc.)和OpenCV使用不同的颜色顺序,因此我们从RGB切换到BGR(蓝色,绿色和红色),这是OpenCV的默认顺序,并且我们返回结果图像:
    return cv2.cvtColor(data, cv2.COLOR_RGB2BGR)

    对于不是.CR2的任何内容,我们使用OpenCV:

    else:
     return cv2.imread(str(path))

现在我们知道了如何将所有图像都放入OpenCV中,是时候开始使用我们拥有的最聪明的算法之一了。

由于相机的动态范围为14位,因此我们将使用相机拍摄的图像:

def load_14bit_gray(path):
 img = load_image(path, bps=16)
 return (cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) / 4).astype(np.uint16)

使用伽玛校正

摄像头传感器捕获的是线性值,即4表示其光强度是1的4倍,而80则是10的8倍。但是JPEG文件格式是否必须使用线性标度? 事实证明并非如此。 因此,如果我们愿意牺牲两个值(例如100和101)之间的差异,则可以在此处拟合另一个值。

为了更好地理解这一点,让我们看一下RAW图像的灰色像素值的直方图。 这是生成该代码的代码-只需加载图像,将其转换为灰度,然后使用pyplot显示直方图:

images = [load_14bit_gray(p) for p in args.images]
 fig, axes = plt.subplots(2, len(images), sharey=False)
 for i, gray in enumerate(images):
 axes[0, i].imshow(gray, cmap='gray', vmax=2**14)
 axes[1, i].hist(gray.flatten(), bins=256)

以下是直方图结果:

我们有两张图片:左边一张是普通图片,您可以看到一些云,但是几乎看不到前景中的任何东西,而右边一张则试图捕获树木中的某些细节,因此剔除了所有的云。 有没有办法结合这些?

如果我们仔细观察直方图,我们会看到在右侧直方图上可见剔除的部分,因为有16,000的值被编码为255,即白色像素。 但是在左侧的图片上,没有白色像素。 我们将14位值编码为8位值的方法非常基础:我们只将这些值除以64(= 26),因此我们失去了2,500和2,501和2,502之间的区别; 相反,我们只有39(255中的),因为8位格式的值必须是整数。

这就是伽马校正的地方。我们将不只是简单地将记录的值显示为强度,而是要进行一些校正,以使图像更具视觉吸引力。

我们将使用非线性函数来强调我们认为更重要的部分:

O=(I255)γ×255O=\left(\frac{I}{255}\right)^{\gamma}\times255

让我们尝试将这个公式形象化为两个不同的值 -γ=0.3γ=3\gamma=0.3和\gamma=3

如您所见,较小的伽玛值强调较低的值; 从0-50的像素值映射到从0-150的像素值(超过可用值的一半)。 对于较高的伽玛,情况恰恰相反:将200-250的值映射到100-250的值(超过可用值的一半)。 因此,如果要使照片更亮,则应选择γ<1的伽玛值,通常称为伽玛压缩。 如果要使照片变暗以显示更多细节,则应选择γ> 1的伽玛值,称为伽玛扩展。

我们可以从浮点数开始到O,而不是对I使用整数,然后将该数字转换为整数以减少更少的信息。 让我们编写一些Python代码来实现伽玛校正:

  1. 首先,让我们编写一个函数来应用公式。 因为我们使用的是14位数字,所以我们必须将其更改为以下内容:
    O=(I214)γ×255O=\left(\frac{I}{2^{14}}\right) ^{\gamma}\times 255

    因此,相关代码如下:

    @functools.lru_cache(maxsize=None)
    def gamma_transform(x, gamma, bps=14):
     return np.clip(pow(x / 2**bps, gamma) * 255.0, 0, 255)

    在这里,我们使用了@ functools.lru_cache装饰器来确保我们不会两次计算任何东西。

  1. 然后,我们仅遍历所有像素并应用转换函数:
    def apply_gamma(img, gamma, bps=14):
     corrected = img.copy()
     for i, j in itertools.product(range(corrected.shape[0]),
     range(corrected.shape[1])):
     corrected[i, j] = gamma_transform(corrected[i, j], gamma,
    bps=bps)
     return corrected

现在,让我们看一下如何使用它来显示新图像以及规则转换的8位图像。 我们将为此编写一个脚本:

了解高动态范围成像

探索改变曝光度的方法

使用多重曝光图像生成HDR图像

使用OpenCV编写HDR脚本

显示HDR影像

了解全景拼接

编写脚本参数并过滤图像

找出相对位置和最终图片尺寸

改善全景拼接


阅读完整文档


文章分类: 编程碎语PythonOpenCV