我正在尝试使用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创建马赛克,但我无法写入临时图像。
如何使这个镶嵌程序有效?
答案 0 :(得分:2)
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的内存中。