AnyMath 文档
Notebook

使用像素矩阵

In [ ]:
Pkg.add(["Images", "FileIO", "ImageShow"])
using Images, FileIO, ImageShow

使用包的图像 Images 它们是多维数组-实际上是矩阵,其中每个元素都包含一个颜色值(例如, RGB这种方法可以很容易地使用标准数组操作来操作像素:索引,切片,零碎计算等。

在这个例子中,我们解决了以下问题:上传任意图像,将其分成相等的垂直条纹,然后将奇数的条纹和偶数的条纹分别组装,形成两个新的图像。 最后,我们并排显示它们以进行视觉比较。

这个例子清楚地说明了:

-如何上传和显示图像;
-如何获得它的尺寸;
-如何计算车道的数量及其宽度,考虑到其余部分;
-如何创建新的数组(图像),并从原来的像素填充它们;
-如何使用循环和条件运算符来处理每个像素。

因此,我们学习与矩阵一样处理图像的基本技术,这是更复杂的计算机视觉和图形处理算法的基础,代码是使用包编写的。 Images, FileIOImageShow.

步骤1。 上传图片并获取其特征

In [ ]:
original_img = load("IMG.jpg")
display(original_img)
height, width = size(original_img)
println("height: $height, width: $width")
No description has been provided for this image
height: 980, width: 1960

在这个街区,我们:

-从文件上传图像 IMG.jpg 使用函数 load 从包 FileIO. 结果保存到变量中 original_img. 现在 original_img 是一个多维数组(矩阵),其中的每个元素都包含格式中的像素颜色 RGB (或灰色阴影,如果图像是黑色和白色)。
-我们使用在屏幕上显示图像 display. 这是可能的感谢包 ImageShow,其提供图形对象的显示。
-我们得到图像的尺寸: height (高度、行数)及 width (宽度,列数)。 功能 size 返回一个元组,我们将其解压缩为两个变量。
-使用以下方法将接收到的值打印到控制台 println. 这使您可以确保图像已正确上传并找出其分辨率。

重要提示:对于彩色图像的工作,包 Images 它会自动将它们表示为类型元素的矩阵 RGB (或 RGB4). 每个这样的元素都包含字段 r, g, b (红色,绿色,蓝色),值从0到1。 因此,我们可以访问单个像素,例如 original_img[100, 200] 将给出行100,列200中的像素的颜色。

在这个阶段,我们已经准备了初步数据以供进一步处理。

第二步。 定义波段的参数

In [ ]:
n_stripes = min(height, width)
base_width = div(width, n_stripes)
remainder = width % n_stripes
widths = [i <= remainder ? base_width + 1 : base_width for i in 1:n_stripes]
max_width = maximum(widths)
odd_stripes = []
even_stripes = []
start_col = 1;

在这里,我们准备了将图像分成垂直条纹所需的一切。:

  • n_stripes = min(height, width)
    条纹的数量被选择为等于图像的最小尺寸。 这确保不会有比行或列更多的条纹,并且每个条纹将具有足够的宽度(以像素为单位)以供可见性。

  • base_width = div(width, n_stripes)
    我们将基本带宽计算为总宽度除以车道数的整数。 由于宽度可能不能完全整除,因此一些条纹将宽一个像素。

  • remainder = width % n_stripes
    除法的剩余部分是一个额外的像素将具有多少条纹。

  • widths = [i <= remainder ? base_width + 1 : base_width for i in 1:n_stripes]
    创建数组 widths,每条车道的位置 i 其宽度指定。 第一个 remainder 条纹的宽度为 base_width + 1,其余的 — base_width. 因此,所有条纹具有几乎相同的宽度,最多相差1个像素,这使得它们能够均匀地复盖图像的整个宽度。

  • max_width = maximum(widths)
    找到所有车道中的最大宽度。 稍后当我们创建用于存储条纹的图像时,这将是必需的:每个条纹将由相同高度(等于原始图像的高度)和相同宽度的矩形表示。 max_width 这样它们就可以很容易地粘在一起。 较窄的条纹将由右侧的黑色像素补充。

  • **odd_stripes = []**及 even_stripes = []
    初始化两个空数组来存储条带本身:在 odd_stripes 我们将添加奇数的条纹,在 even_stripes -用偶数。

  • start_col = 1
    计数器变量,其将在下一次通过期间指示原始图像中下一个条纹的开始。

在这个阶段,我们已经充分准备了条带提取周期的数据:我们知道它们有多少,它们有多宽,我们准备通过奇偶校验来划分它们。

第三步。 从原始图像中提取条纹

In [ ]:
for i in 1:n_stripes
    w = widths[i]
    end_col = start_col + w - 1
    stripe_rect = original_img[:, start_col:end_col]
    stripe_img = similar(original_img, height, max_width)
    fill!(stripe_img, RGB(0, 0, 0))
    stripe_img[:, 1:w] = stripe_rect
    if isodd(i)
        push!(odd_stripes, stripe_img)
    else
        push!(even_stripes, stripe_img)
    end
    start_col += w
end

在这个循环中,我们依次从左到右穿过所有车道,并执行以下操作:

-定义车道的边界:
使用当前值 start_col (左边缘)和宽度 w,我们计算 end_col -车道的右边缘。 这样我们就知道原始图像的哪个列范围对应于第i个波段。

-切出矩形:
stripe_rect = original_img[:, start_col:end_col] -我们把所有的线路(:)和必要的列。 结果是一个大小的矩阵 height × w,包含给定条带的像素。

-创建标准化矩形:
stripe_img = similar(original_img, height, max_width) 创建与原始矩阵类型相同但大小相同的新矩阵(图像) height × max_width. 这是必要的,以便所有条纹具有相同的宽度—然后它们将很容易粘合到最终图像中。 img1img2.
然后 fill!(stripe_img, RGB(0,0,0)) 用黑色填充此矩形(所有分量均为0)。

-复制条带的像素:
stripe_img[:, 1:w] = stripe_rect -将切出的片段插入创建的矩形的左侧部分。 如果条纹比 max_width,黑色像素将保留在右侧(背景)。

-按奇偶排序:
使用 isodd(i) 我们检查车道号码. 将奇数条纹添加到阵列 odd_stripes,偶数-in even_stripes. 这就是我们如何将条纹分成两组,以便稍后创建两个单独的图像。

-转移开始:
start_col += w -我们通过新处理的条带的宽度增加计数器,以便在下一次迭代中我们可以从当前条带结束的地方开始。

因此,在循环之后,我们有两个数组,每个数组都包含一组相同大小的矩阵(条带)。 height × max_width,而且,来自源图像的所有像素按照它们的顺序沿着这些条纹分布。

第四步。 我们从不同奇偶校验的条纹形成两个图像

In [ ]:
img1 = similar(original_img, height, max_width * length(odd_stripes))
img2 = similar(original_img, height, max_width * length(even_stripes))
fill!(img1, RGB(0, 0, 0))
fill!(img2, RGB(0, 0, 0))
col_offset = 1
for stripe in odd_stripes
    w = size(stripe, 2)
    img1[:, col_offset:col_offset+w-1] = stripe
    col_offset += w
end
col_offset = 1
for stripe in even_stripes
    w = size(stripe, 2)
    img2[:, col_offset:col_offset+w-1] = stripe
    col_offset += w
end

在这个阶段,我们将单个条纹组合成两个整体图像。:

-创建画布:
img1img2 使用 similar,从而保证了与原始图像相同的像素类型。 它们的高度等于原始的高度(height),并且宽度计算为 max_width (最大带宽)乘以相应组的车道数。
例如,如果我们有10条车道,那么在 odd_stripes 将有5条条纹(奇数),这意味着宽度是 img1 = max_width * 5.
然后两个图像都填充黑色(RGB(0,0,0))-这将是我们将复盖条纹像素的背景。

-从奇数条纹组装图像:
变量 col_offset 跟踪最终图像中的当前位置(列),从1开始。 在一个循环中,从 odd_stripes 我们:

-确定当前车道的实际宽度 w (它等于 max_width 由于所有的条纹都扩大到这个尺寸);
-插入带状矩阵 stripe 到适当范围的列 img1[:, col_offset:col_offset+w-1];
-放大 col_offsetw 移动到下一个车道的位置。

-从偶数条纹组装图像:
对于执行类似的循环 even_stripes,形成 img2.

结果,我们得到两个图像,每个图像代表原始图像的水平胶合条带。:

  • img1 包含带1,3,5,。.. (奇数);
  • img2 包含带2,4,6,。.. (甚至)。

请注意:由于所有条纹已缩小到单一宽度 max_width,在 img1img2 碎片之间没有间隙-它们靠近在一起。 较窄条纹右侧的黑色区域(如果条纹的原始宽度较小 max_width)被保留,因此在关节处可能存在垂直的黑色条纹—这是对齐的伪影,但它不会干扰进一步的比较。

第五步。 合并两幅图像进行比较并输出结果

In [ ]:
h1, w1 = size(img1)
h2, w2 = size(img2)
max_h = max(h1, h2)
side_by_side = similar(img1, max_h, w1 + w2 + 10)
fill!(side_by_side, RGB(1, 1, 1))
side_by_side[1:h1, 1:w1] = img1
side_by_side[1:h2, w1+11:w1+w2+10] = img2
display(side_by_side)
println("height_img1: $h1, height_img2: $h2")
println("width_img1: $w1, width_img2: $w2")
No description has been provided for this image
height_img1: 980, height_img2: 980
width_img1: 980, width_img2: 980

在最后阶段,我们:

-我们得到生成的图像的尺寸:
h1, w1 = size(img1)h2, w2 = size(img2). 两个图像具有相同的高度(height 原始图像),但如果奇数和偶数条纹的数量不同(例如,如果条纹的总数是奇数),则宽度可能会有所不同。 在我们的例子中,两者都是每个980像素,因为原始宽度是1960,条纹数量是980,并且有同样的奇数/偶数。

-创建共享画布 side_by_side:
max_h = max(h1, h2) -选择最大高度(它们相等,但代码是通用的)。
side_by_side = similar(img1, max_h, w1 + w2 + 10) -创建相同类型的画布图像 img1,身高 max_h 和宽度等于两个图像的宽度之和加上10个像素的间隙。
fill!(side_by_side, RGB(1,1,1)) -用白色填充画布(所有组件等于1)。 白色背景在两个图像之间创建视觉分隔符。

-我们在画布上放置图像:
side_by_side[1:h1, 1:w1] = img1 -复制它 img1 到画布的左侧(第1行:h1,第1列:w1)。
side_by_side[1:h2, w1+11:w1+w2+10] = img2 -插入 img2 右边,从位置开始 w1+11 (我们留下10个像素的空白后 img1,加上索引的一个像素-因此 +11). 列的范围精确捕获 w2 列。

-显示结果:
display(side_by_side) 它显示了最终的合成图像,其中奇数的条纹在左边,偶数的条纹在右边。 它们之间的白色条纹可以很容易地在视觉上分离图片。

-我们将尺寸输出到控制台:
println 告诉我们接收到的图像的确切尺寸。 这对于检查和理解原始图像是如何转换的很有用。

结论

我们已经从上传图像到可视化其不寻常的"切割"结果。 这个例子清楚地展示了如何使用它们作为普通矩阵来处理图像:提取片段,创建新数组,用像素填充它们并将它们粘合在一起。 这种方法是许多计算机视觉和图形处理任务的基础,从简单的裁剪到复杂的过滤器和转换。

**有趣的事实:**尽管我们将原始图像分成两组条纹(奇数和偶数),但在视觉上新图像看起来完全相同,这是由于图像的原始对称性。