我正在编写一个Python(3.4.3)程序,该程序在Ubuntu 14.04 LTS上使用VIPS(8.1.1)来读取使用多个线程的许多小块并将它们组合成一个大图像。
在一个非常简单的测试中:
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Lock
from gi.repository import Vips
canvas = Vips.Image.black(8000,1000,bands=3)
def do_work(x):
img = Vips.Image.new_from_file('part.tif') # RGB tiff image
with lock:
canvas = canvas.insert(img, x*1000, 0)
with ThreadPoolExecutor(max_workers=8) as executor:
for x in range(8):
executor.submit(do_work, x)
canvas.write_to_file('complete.tif')
我得到了正确的结果。在我的完整程序中,每个线程的工作涉及从源文件读取二进制文件,将它们转换为tiff格式,读取图像数据并插入到画布中。它似乎工作,但当我试图检查结果时,我遇到了麻烦。因为图像非常大(~50000 * 100000像素),我无法将整个图像保存在一个文件中,所以我试过了
canvas = canvas.resize(.5)
canvas.write_to_file('test.jpg')
这需要很长时间,并且生成的jpeg只有黑色像素。如果我调整三次,程序就会被杀死。我也试过
canvas.extract_area(20000,40000,2000,2000).write_to_file('test.tif')
这会导致错误消息segmentation fault(core dumped)
,但会保存图像。其中有图像内容,但它们似乎位于错误的位置。
我想知道问题是什么?
以下是完整计划的代码。使用OpenCV + sharedmem(sharedmem处理多处理部分)也实现了相同的逻辑,并且它没有问题。
import os
import subprocess
import pickle
from multiprocessing import Lock
from concurrent.futures import ThreadPoolExecutor
import threading
import numpy as np
from gi.repository import Vips
lock = Lock()
def read_image(x):
with open(file_name, 'rb') as fin:
fin.seek(sublist[x]['dataStartPos'])
temp_array = np.fromfile(fin, dtype='int8', count=sublist[x]['dataSize'])
name_base = os.path.join(rd_path, threading.current_thread().name + 'tempimg')
with open(name_base + '.jxr', 'wb') as fout:
temp_array.tofile(fout)
subprocess.call(['./JxrDecApp', '-i', name_base + '.jxr', '-o', name_base + '.tif'])
temp_img = Vips.Image.new_from_file(name_base + '.tif')
with lock:
global canvas
canvas = canvas.insert(temp_img, sublist[x]['XStart'], sublist[x]['YStart'])
def assemble_all(filename, ramdisk_path, scene):
global canvas, sublist, file_name, rd_path, tilesize_x, tilesize_y
file_name = filename
rd_path = ramdisk_path
file_info = fetch_pickle(filename) # A custom function
# this info includes where to begin reading image data, image size and coordinates
tilesize_x = file_info['sBlockList_P0'][0]['XSize']
tilesize_y = file_info['sBlockList_P0'][0]['YSize']
sublist = [item for item in file_info['sBlockList_P0'] if item['SStart'] == scene]
max_x = max([item['XStart'] for item in file_info['sBlockList_P0']])
max_y = max([item['YStart'] for item in file_info['sBlockList_P0']])
canvas = Vips.Image.black((max_x+tilesize_x), (max_y+tilesize_y), bands=3)
with ThreadPoolExecutor(max_workers=4) as executor:
for x in range(len(sublist)):
executor.submit(read_image, x)
return canvas
在驱动程序脚本中调用上述模块(导入为mcv):
canvas = mcv.assemble_all(filename, ramdisk_path, 0)
为了检查内容,我使用了
canvas.extract_area(25000, 40000, 2000, 2000).write_to_file('test_vips1.jpg')
答案 0 :(得分:2)
我认为你的问题与vips计算像素的方式有关。
在像OpenCV这样的系统中,您可以对图像执行一系列操作。每个操作都会进行大量计算并以某种方式修改内存。
Vips不是这样的,虽然界面看起来很相似。在vips中,当您对图像执行操作时,实际上只是在管道中添加了新的部分。只有当你最终将输出连接到某个接收器(磁盘上的文件,或者你想要填充图像数据的内存区域或显示区域)时,vips才会实际进行任何计算。 vips使用递归算法在管道的整个长度上下运行大量工作者,同时评估您创建的所有操作。
为了与编程语言类比,像OpenCV这样的系统是必不可少的,vips是功能性的。
vips的优点在于它可以立即看到整个流水线,它可以优化大部分内存使用并充分利用你的CPU。糟糕的是,长序列操作可能需要大量内存来评估(而对于像OpenCV这样的系统,您更有可能受到图像大小的限制)。特别是,vips用于评估的递归系统意味着管道长度受到C堆栈的限制,在许多操作系统上约为2MB。
这是一个简单的测试程序,可以或多或少地执行您正在做的事情:
#!/usr/bin/python
import sys
from gi.repository import Vips
if len(sys.argv) < 4:
print "usage: %s image-in image-out n" % sys.argv[0]
print " make an n x n grid of image-in"
sys.exit(1)
tile = Vips.Image.new_from_file(sys.argv[1])
outfile = sys.argv[2]
size = int(sys.argv[3])
img = Vips.Image.black(size * tile.width, size * tile.height, bands = 3)
for y in range(size):
for x in range(size):
img = img.insert(tile, x * size, y * size)
# we're not interested in huge files for this test, just write a small patch
img.crop(10, 10, 100, 100).write_to_file(outfile)
你这样运行:
$ time ./bigjoin.py ~/pics/k2.jpg out.tif 2
real 0m0.176s
user 0m0.144s
sys 0m0.031s
加载k2.jpg
(2k x 2k JPG图像),将该图像重复为2 x 2网格,并保存一小部分。此程序适用于非常大的图像,请尝试删除crop
并运行为:
$ ./bigjoin.py huge.tif out.tif[bigtiff] 10
它将巨大的tiff图像复制100次到一个非常巨大的tiff文件中。它会很快并且使用很少的内存。
然而,这个程序会因为多次复制小图像而变得非常不满意。例如,在这台机器(Mac)上,我可以运行:
$ ./bigjoin.py ~/pics/k2.jpg out.tif 26
但这失败了:
$ ./bigjoin.py ~/pics/k2.jpg out.tif 28
Bus error: 10
输出为28 x 28,即784个瓦片。我们构建图像的方式,重复插入单个图块,管道784操作的时间长 - 足以导致堆栈溢出。在我的Ubuntu笔记本电脑上,我可以在开始失败之前获得长达约1,400次操作的管道。
有一个简单的方法来修复这个程序:构建一个广泛而不是深层的管道。不是每次都插入一个图像,而是制作一组条带,然后连接条带。现在,管道深度将与瓦片数量的平方根成比例。例如:
img = Vips.Image.black(size * tile.width, size * tile.height, bands = 3)
for y in range(size):
strip = Vips.Image.black(size * tile.width, tile.height, bands = 3)
for x in range(size):
strip = strip.insert(tile, x * size, 0)
img = img.insert(strip, 0, y * size)
现在我可以跑了:
$ ./bigjoin2.py ~/pics/k2.jpg out.tif 200
这是连接在一起的40,000张图片。