【问题标题】:Making a huge image mosaic with pyvips使用 pyvips 制作巨大的图像马赛克
【发布时间】:2017-12-17 05:12:50
【问题描述】:

我正在尝试使用 pyvips 制作图像马赛克生成器。所以基本上,给定一个图像(在下面称为原始图像)创建一个新的、更大的图像,它类似于原始图像,除了每个像素(或更实际的像素组)被更小的不同图像块替换。
我被 pyvips 所吸引,因为据说它可以处理巨大的图像,并且它可以处理图像而无需将它们完全加载到内存中。 但是,我在创建空白马赛克然后填充平铺图像时遇到问题。
在下面的代码中,我尝试将图块逐行连接在一起以创建马赛克,但不幸的是,这段代码会占用我的 RAM 并且总是出现段错误。

import os
import pyvips
from os.path import join
from scipy.spatial import cKDTree

class Mosaic(object):

    def __init__(self, dir_path, original_path, tree=None, averages=None):
        self.dir_path = dir_path
        self.original = original_path
        self.tree = tree
        if averages:
            self.averages = averages
        else:
            self.averages = {}

    def get_image(self, path):
        return pyvips.Image.new_from_file(path, access="sequential")

    def build_tree(self):
        for root, dirs, files in os.walk(self.dir_path):
            print('Loading images from', root, '...')
            for file_name in files:
                path = join(root, file_name)
                try:
                    image = pyvips.Image.new_from_file(path)
                    self.averages[self.avg_rgb(image)] = path
                except pyvips.error.Error:
                    print('File', path, 'not recognized as an image.')
        self.tree = cKDTree(self.averages.keys())
        print('Loaded', len(self.averages), 'images.')

    def avg_rgb(self, image):
        m = image.stats()
        return tuple(m(4,i)[0] for i in range(1,4))

    def get_tile_name(self, patch):
        avg = self.avg_rgb(patch)
        index = self.tree.query(avg)[1]
        return self.averages[tuple(self.tree.data[index])]

    def get_tile(self, x, y, step):
        patch = self.get_image(self.original).crop(x, y, step, step)
        patch_name = self.get_tile_name(patch)
        return pyvips.Image.new_from_file(patch_name, access="sequential")

    def make_mosaic(self, tile_num, tile_size, mosaic_path):
        original = self.get_image(self.original)
        mosaic = None
        step = min(original.height, original.width) / tile_num
        for y in range(0, original.height, step):
            mosaic_row = None
            print('Building row', y/step, '/', original.height/step)
            for x in range(0, original.width, step):
                tile = self.get_tile(x, y, step)
                tile = tile.resize(float(tile_size) / float(min(tile.width, tile.height)))
                tile = tile.crop(0, 0, tile_size, tile_size)
                #mosaic.draw_image(tile, x, y)
                mosaic_row = tile if not mosaic_row else mosaic_row.join(tile, "horizontal")
            mosaic = mosaic_row if not mosaic else mosaic.join(mosaic_row, "vertical")
        mosaic.write_to_file(mosaic_path)

我也尝试通过调整原始图像的大小然后使用如下所示的 draw_image 来创建马赛克,但这也会崩溃。

mosaic = self.get_image(self.original).resize(tile_size)

mosaic.draw_image(tile, x, y)

最后,我尝试从 new_temp_file 创建马赛克,但无法写入临时图像。

我怎样才能使这个马赛克程序工作?

【问题讨论】:

  • libvips 使用递归算法来计算接下来要计算的像素。这意味着如果您的图像管道变得很长,它可能会溢出 C 堆栈并导致崩溃。看看arrayjoin:这是一个 libvips 操作,可以在一个步骤中将大量图像连接在一起。我一次加入了 10,000 多个,没有任何问题。 jcupitt.github.io/libvips/API/current/…
  • 这是个好主意,但是,我无法像在文档中那样工作。当我在图像上调用 arrayjoin 时,它不接受任何参数,并且似乎将每个波段分开,制作每个波段的灰度图像。即:我最终得到了 3 张黑白图像,它们连接在一起......
  • 我会在接下来的几天里尝试为你制作一个示例程序。
  • 谢谢!我的目标是为圣诞礼物制作一个巨大的马赛克!节日快乐
  • 天哪 :( 我的回答可能为时已晚。无论如何,祝你快乐。

标签: python image image-processing vips


【解决方案1】:

libvips 使用递归算法来确定接下来要计算哪些像素,因此对于非常长的管道,您可能会溢出 C 堆栈并导致崩溃。

最简单的解决方案是使用arrayjoin。这是一个 libvips 运算符,可以在一次调用中加入许多图像:

http://jcupitt.github.io/libvips/API/current/libvips-conversion.html#vips-arrayjoin

libvips github上有一个使用它一次加入30,000张图片的例子:

https://github.com/jcupitt/libvips/issues/471

(虽然这是使用以前版本的 libvips Python 绑定)

我调整了您的程序以使用 arrayjoin,并更改了它加载图像的方式。我注意到您还为每个输出图块重新加载了原始图像,因此删除它可以提高速度。

#!/usr/bin/python2

from __future__ import print_function
import os
import sys
import pyvips
from os.path import join
from scipy.spatial import cKDTree

class Mosaic(object):

    def __init__(self, dir_path, original_path, tile_size=128, tree=None, averages=None):
        self.dir_path = dir_path
        self.original_path = original_path
        self.tile_size = tile_size
        self.tree = tree
        if averages:
            self.averages = averages
        else:
            self.averages = {}

    def avg_rgb(self, image):
        m = image.stats()
        return tuple(m(4,i)[0] for i in range(1,4))

    def build_tree(self):
        for root, dirs, files in os.walk(self.dir_path):
            print('Loading images from', root, '...')
            for file_name in files:
                path = join(root, file_name)
                try:
                    # load image as a square image of size tile_size X tile_size
                    tile = pyvips.Image.thumbnail(path, self.tile_size,
                                                  height=self.tile_size,
                                                  crop='centre')
                    # render into memory
                    tile = tile.copy_memory()
                    self.averages[self.avg_rgb(tile)] = tile
                except pyvips.error.Error:
                    print('File', path, 'not recognized as an image.')
        self.tree = cKDTree(self.averages.keys())
        print('Loaded', len(self.averages), 'images.')

    def fetch_tree(self, patch):
        avg = self.avg_rgb(patch)
        index = self.tree.query(avg)[1]

        return self.averages[tuple(self.tree.data[index])]

    def make_mosaic(self, tile_num, mosaic_path):
        mosaic = None
        original = pyvips.Image.new_from_file(self.original_path)
        step = min(original.height, original.width) / tile_num
        tiles_across = original.width / step
        tiles_down = original.height / step
        tiles = []
        for y in range(0, tiles_down):
            print('Building row', y, '/', tiles_down)
            for x in range(0, tiles_across):
                patch = original.crop(x * step, y * step, 
                                      min(step, original.width - x * step), 
                                      min(step, original.height - y * step)) 
                tile = self.fetch_tree(patch) 
                tiles.append(tile)

        mosaic = pyvips.Image.arrayjoin(tiles, across=tiles_across)

        print('writing ', mosaic_path)
        mosaic.write_to_file(mosaic_path)

mosaic = Mosaic(sys.argv[1], sys.argv[2])
mosaic.build_tree()
mosaic.make_mosaic(200, sys.argv[3])

我可以这样运行:

$ time ./mosaic2.py samples/ k2.jpg x.png
Loading images from samples/ ...
Loaded 228 images.
Building row 0 / 292
...
Building row 291 / 292
writing  x.png
real    7m19.333s
user    7m27.322s
sys     0m30.578s

制作一个 26496 x 37376 像素的图像,在这种情况下,它运行在大约 150mb 的内存中。

【讨论】:

  • 约翰,圣诞快乐!
  • 这看起来棒极了!太感谢了。一个快速的问题,由于某种原因,我得到了一个 VipsOperation: class "thumbnail" not found 当我尝试运行程序时,即使一切都已正确安装和更新。事实上,当我调用 pyvips.Image.thumbnail 时,我什至得到了一个函数对象 但是当我尝试调用它时它失败了。您可以指出我的任何想法/事情以解决此问题吗?谢谢你的帮助,我真的很感激。圣诞快乐!
  • 我猜你安装了旧版本的 libvips -- thumbnail 是在 8.5 中添加的。您可以改用new_from_fileresize,但会慢一些。在打开图像进行调整大小时使用sequential
猜你喜欢
  • 1970-01-01
  • 2018-09-02
  • 1970-01-01
  • 2021-10-27
  • 2018-01-03
  • 1970-01-01
  • 1970-01-01
  • 2012-08-28
  • 1970-01-01
相关资源
最近更新 更多