Engee 文档
Notebook

图像的类型和转换

本演示旨在展示指定图像的方法和图像变换的基本原理,尤其是仿射变换。

In [ ]:
Pkg.add(["TestImages", "ImageShow"])
In [ ]:
using Images # Библиотека обработки изображений
using ImageShow # Библиотека отрисовки изображений
using TestImages # Библиотека тестовых изображений

色彩空间类型

任何图像都是由像素对象组成的简单数组。图像的元素称为像素,Julia Images 将像素视为一级对象。例如,我们有灰度的灰色像素、RGB 彩色像素和 Lab 彩色像素。

让我们从 RGB 格式(由英文单词 red、green、blue - 红、绿、蓝组成的缩写)开始分析--这是一种加法色彩模型,描述了使用三种颜色(通常称为三原色)进行色彩再现的编码方式。三原色的选择取决于眼睛视网膜对色彩感知的生理特性。

In [ ]:
img_rgb = [RGB(1.0, 0.0, 0.0), RGB(0.0, 1.0, 0.0), RGB(0.0, 0.0, 1.0)]
Out[0]:
No description has been provided for this image
In [ ]:
dump(img_rgb)
Array{RGB{Float64}}((3,))
  1: RGB{Float64}
    r: Float64 1.0
    g: Float64 0.0
    b: Float64 0.0
  2: RGB{Float64}
    r: Float64 0.0
    g: Float64 1.0
    b: Float64 0.0
  3: RGB{Float64}
    r: Float64 0.0
    g: Float64 0.0
    b: Float64 1.0

灰色--是一种单色矩阵,以灰色阴影描述图像。默认使用 8 位色彩编码。

In [ ]:
img_gray = rand(Gray, 3, 3)
Out[0]:
No description has been provided for this image
In [ ]:
dump(img_gray)
Array{Gray{Float64}}((3, 3))
  1: Gray{Float64}
    val: Float64 0.20548107200013277
  2: Gray{Float64}
    val: Float64 0.7190843096024139
  3: Gray{Float64}
    val: Float64 0.7675684128936745
  4: Gray{Float64}
    val: Float64 0.4840881004197154
  5: Gray{Float64}
    val: Float64 0.21892864471268947
  6: Gray{Float64}
    val: Float64 0.5776559337062981
  7: Gray{Float64}
    val: Float64 0.855915975791712
  8: Gray{Float64}
    val: Float64 0.7152103286181891
  9: Gray{Float64}
    val: Float64 0.9976538325486705

LAB 是两种不同(尽管相似)色彩空间名称的缩写。比较著名和常见的是 CIELAB(更准确地说,是 CIE 1976 Lab*),另一个是 Hunter Lab(更准确地说,是 Hunter L、a、b)。因此,Lab 是一种非正式的缩写,并不能明确定义色彩空间。在恩吉,当提到 Lab 空间时,他们指的是 CIELAB。

In [ ]:
img_lab = rand(Lab, 3, 3)
Out[0]:
No description has been provided for this image
In [ ]:
dump(img_gray)
Array{Gray{Float64}}((3, 3))
  1: Gray{Float64}
    val: Float64 0.20548107200013277
  2: Gray{Float64}
    val: Float64 0.7190843096024139
  3: Gray{Float64}
    val: Float64 0.7675684128936745
  4: Gray{Float64}
    val: Float64 0.4840881004197154
  5: Gray{Float64}
    val: Float64 0.21892864471268947
  6: Gray{Float64}
    val: Float64 0.5776559337062981
  7: Gray{Float64}
    val: Float64 0.855915975791712
  8: Gray{Float64}
    val: Float64 0.7152103286181891
  9: Gray{Float64}
    val: Float64 0.9976538325486705

对象类型之间的转换

In [ ]:
Gray.(img_rgb) # RGB => Gray
Out[0]:
No description has been provided for this image
In [ ]:
RGB.(img_gray) # Gray => RGB
Out[0]:
No description has been provided for this image
In [ ]:
RGB.(img_lab) # Lab => RGB
Out[0]:
No description has been provided for this image

图像转换

让我们先从.jpg文件中加载一张图片。

In [ ]:
img = load( "$(@__DIR__)/4028965.jpg" )
Out[0]:
No description has been provided for this image

让我们提高加载图像的对比度。adjust_histogram(Equalisation(),...)*函数可以处理不同类型的输入数据。返回的图像类型与输入类型相对应。对于彩色图像,输入会转换为 YIQ 类型,然后均衡 Y 通道,再将其与 I 和 Q 通道合并。

In [ ]:
alg = Equalization(nbins = 256)
img_adjusted = adjust_histogram(img, alg)
Out[0]:
No description has been provided for this image

相对于源图像,将图像尺寸缩小 4 倍。如下图所示,Imresize 可以使用相对于源图像的关系来调整大小,也可以使用手动调整新图像的尺寸来调整大小:

例如:imresize(img, (400, 400))

In [ ]:
img_small = imresize(img_adjusted, ratio=1/4)
Out[0]:
No description has been provided for this image
In [ ]:
print(size(img_adjusted), " --> ", size(img_small))
(1148, 1243) --> (287, 311)

仿射变换(源自拉丁文 affinis,意为 "接触、接近、相邻")是平面或空间向自身的映射,其中平行线变为平行线,相交线变为相交线,交叉线变为交叉线。基本的图像变换使用索引网格对图像进行操作。根据下图描述的原理,变换由图像变换矩阵定义。 image.png

In [ ]:
# Вспомогательная функция контроля размерностей
function C_B_V(x, max_val)
    x[x .> max_val - 1] .= max_val - 1
    x[x .< 1] .= 1
    return x
end
Out[0]:
C_B_V (generic function with 1 method)

接下来,让我们声明一个仿射图像变换函数,其中

  1. Theta 是变换矩阵; img 是输入图像
  2. out_size 是输出图像的尺寸;
  3. grid 是像素索引网格。
In [ ]:
function transform(theta, img, out_size)
    grid = grid = zeros(3, out_size[1]*out_size[2])
    grid[1, :] = reshape(((-1:2/(out_size[1]-1):1)*ones(1,out_size[2])), 1, size(grid,2))
    grid[2, :] = reshape((ones(out_size[1],1)*(-1:2/(out_size[2]-1):1)'), 1, size(grid,2))
    grid[3, :] = ones(Int, size(grid, 2))

    # Умножение theta на grid
    T_g = theta * grid

    # Вычисление координат x, y
    x = (T_g[1, :] .+ 1) .* (out_size[2]) / 2
    y = (T_g[2, :] .+ 1) .* (out_size[1]) / 2

    # Округление координат
    x0 = ceil.(x)
    x1 = x0 .+ 1
    y0 = ceil.(y)
    y1 = y0 .+ 1

    # Обрезание значений x0, x1, y0, y1
    x0 = C_B_V(x0, out_size[2])
    x1 = C_B_V(x1, out_size[2])
    y0 = C_B_V(y0, out_size[1])
    y1 = C_B_V(y1, out_size[1])

    # Вычисление базовых координат
    base_y0 = y0 .* out_size[1]
    base_y1 = y1 .* out_size[1]

    # Работа с изображением
    im_flat = reshape(img, :)

    # Обрабатываем координаты
    A = (x1 .- x) .* (y1 .- y) .* im_flat[Int.(base_y0 .+ x0 .+ 1)]
    B = (x1 .- x) .* (y .- y0) .* im_flat[Int.(base_y1 .+ x0 .+ 1)]
    C = (x .- x0) .* (y1 .- y) .* im_flat[Int.(base_y0 .+ x1 .+ 1)]
    D = (x .- x0) .* (y .- y0) .* im_flat[Int.(base_y1 .+ x1 .+ 1)]

    # Расчет результата
    result = reshape((A .+ B .+ C .+ D), (out_size[1], out_size[2]))
    return result
end
Out[0]:
transform (generic function with 1 method)

首先,让我们将此函数应用于灰度图像。

In [ ]:
img_sg = Gray.(img_small)
Out[0]:
No description has been provided for this image

从下面的数据中我们可以看到,灰度图像的色彩分辨率为 8 位,其维度仅由宽度和高度表示。

In [ ]:
dump(img_sg[1])
Gray{N0f8}
  val: N0f8
    i: UInt8 0x0e
In [ ]:
size(img_sg)
Out[0]:
(287, 311)

让我们来设置该图像的变换矩阵。

In [ ]:
theta = [2 0.3 0; -0.3 2 0]
Out[0]:
2×3 Matrix{Float64}:
  2.0  0.3  0.0
 -0.3  2.0  0.0

将我们的函数应用于图像。我们可以看到,图像的大小减半并进行了旋转。

In [ ]:
img_transfor = transform(theta, img_sg, [size(img_sg,1),size(img_sg,2)])
Out[0]:
No description has been provided for this image

为了得到逆变换,我们从变换矩阵中找出逆矩阵,并将其四舍五入到第四位。

In [ ]:
theta_inv = hcat(inv(theta[1:2,1:2]), [-0.1;0.1])
theta_inv = round.(theta_inv.*10^4)./10^4
Out[0]:
2×3 Matrix{Float64}:
 0.489   -0.0733  -0.1
 0.0733   0.489    0.1
In [ ]:
img_sg_new = transform(theta_inv, img_transfor, [size(img_transfor,1),size(img_transfor,2)])
Out[0]:
No description has been provided for this image

现在,让我们将此函数应用于 RGB 图像。首先,我们将 RGB 图像转换为通道表示法。让我们来分析一下这种图像表示法为我们带来的可能性。

In [ ]:
img_CHW = channelview(img_small);
print(size(img_small), " --> ", size(img_CHW))
(287, 311) --> (3, 287, 311)

选择图像的红色通道,只绘制红色通道。

In [ ]:
RGB.(img_CHW[1,:,:], 0.0, 0.0) # red
Out[0]:
No description has been provided for this image

如果所有通道都均匀分布,我们就会得到灰色调的图像,因为没有任何一种基色比其他颜色更重要。

In [ ]:
RGB.(img_CHW[1,:,:], img_CHW[1,:,:], img_CHW[1,:,:]) # Gray
Out[0]:
No description has been provided for this image

在通道图像示例的基础上,我们可以将 RGB 图像作为三个独立的一维矩阵通过我们的函数运行,并将其结果合并,从而实现 RGB 图像的仿射变换。

In [ ]:
img_CHW_new = zeros(size(img_CHW))

for i in 1:size(img_CHW,1)
   img_CHW_new[i,:,:] = transform(theta, img_CHW[i,:,:], [size(img_CHW,2),size(img_CHW,3)])
end 

RGB.(img_CHW_new[1,:,:], img_CHW_new[2,:,:], img_CHW_new[3,:,:])
Out[0]:
No description has been provided for this image

结论

在本演示中,我们讨论了图像的通道表示法和不同类型的矩阵表示法,并分析了Engee中的一些图像处理功能。