私はPythonでフォトギャラリーを構築しており、高解像度画像のサムネイルをすばやく生成できるようにしたいと考えています。
さまざまな画像ソースの高品質サムネイルを生成する最も速い方法は何ですか?
Imagemagickなどの外部ライブラリを使用する必要がありますか、またはこれを行う効率的な内部方法はありますか?
サイズ変更された画像のサイズは(最大サイズ)になります。
120x120
720x720
1600x1600
元の色をできるだけ多く保持し、圧縮によるアーティファクトを最小限に抑えたいので、品質が問題になります。
ありがとう。
あなたはそれが簡単にこれを行うPILが欲しい
from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = ['a.jpg','b.jpg','c.jpg']
for image in files:
for size in sizes:
im = Image.open(image)
im.thumbnail(size)
im.save("thumbnail_%s_%s" % (image, "_".join(size)))
必死にスピードが必要なら。次に、スレッド化、マルチプロセス、または別の言語を取得します。
私は楽しいと思ったので、上記で提案されたさまざまな方法といくつかの自分のアイデアについてベンチマークを行いました。
私は、それぞれ4032x3024ピクセルの高解像度12MP iPhone 6S画像を1000個集め、8コアiMacを使用しました。
ここにテクニックと結果があります-それぞれ独自のセクションにあります。
方法1-順次ImageMagick
これは単純化された、最適化されていないコードです。各画像が読み取られ、サムネイルが作成されます。その後、再度読み取られ、異なるサイズのサムネイルが生成されます。
#!/bin/bash
start=$SECONDS
# Loop over all files
for f in image*.jpg; do
# Loop over all sizes
for s in 1600 720 120; do
echo Reducing $f to ${s}x${s}
convert "$f" -resize ${s}x${s} t-$f-$s.jpg
done
done
echo Time: $((SECONDS-start))
結果:170秒
方法2-単一のロードと連続的なサイズ変更を伴う順次ImageMagick
これは引き続きシーケンシャルですが、少しスマートです。各画像は1回だけ読み取られ、読み込まれた画像は3倍にサイズ変更され、3つの解像度で保存されます。改善点は、各画像が3回ではなく1回だけ読み取られることです。
#!/bin/bash
start=$SECONDS
# Loop over all files
N=1
for f in image*.jpg; do
echo Resizing $f
# Load once and successively scale down
convert "$f" \
-resize 1600x1600 -write t-$N-1600.jpg \
-resize 720x720 -write t-$N-720.jpg \
-resize 120x120 t-$N-120.jpg
((N=N+1))
done
echo Time: $((SECONDS-start))
結果:76秒
方法3-GNU Parallel + ImageMagick
これは、前の方法に基づいて、GNU Parallelを使用してN
イメージを並列処理します。ここで、N
はマシン上のCPUコアの数です。
#!/bin/bash
start=$SECONDS
doit() {
file=$1
index=$2
convert "$file" \
-resize 1600x1600 -write t-$index-1600.jpg \
-resize 720x720 -write t-$index-720.jpg \
-resize 120x120 t-$index-120.jpg
}
# Export doit() to subshells for GNU Parallel
export -f doit
# Use GNU Parallel to do them all in parallel
parallel doit {} {#} ::: *.jpg
echo Time: $((SECONDS-start))
結果:18秒
方法4-GNU Parallel + vips
これは前の方法と同じですが、コマンドラインでImageMagickの代わりにvips
を使用します。
#!/bin/bash
start=$SECONDS
doit() {
file=$1
index=$2
r0=t-$index-1600.jpg
r1=t-$index-720.jpg
r2=t-$index-120.jpg
vipsthumbnail "$file" -s 1600 -o "$r0"
vipsthumbnail "$r0" -s 720 -o "$r1"
vipsthumbnail "$r1" -s 120 -o "$r2"
}
# Export doit() to subshells for GNU Parallel
export -f doit
# Use GNU Parallel to do them all in parallel
parallel doit {} {#} ::: *.jpg
echo Time: $((SECONDS-start))
結果:8秒
方法5-順次PIL
これはヤコブの答えに対応するためのものです。
#!/usr/local/bin/python3
import glob
from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = glob.glob('image*.jpg')
N=0
for image in files:
for size in sizes:
im=Image.open(image)
im.thumbnail(size)
im.save("t-%d-%s.jpg" % (N,size[0]))
N=N+1
結果:38秒
方法6-単一のロードと連続的なサイズ変更を伴う順次PIL
これはJakobの回答の改善を目的としています。画像を1回だけロードしてから、3回リサイズして、毎回再ロードして新しい解像度を生成するのではありません。
#!/usr/local/bin/python3
import glob
from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = glob.glob('image*.jpg')
N=0
for image in files:
# Load just once, then successively scale down
im=Image.open(image)
im.thumbnail((1600,1600))
im.save("t-%d-1600.jpg" % (N))
im.thumbnail((720,720))
im.save("t-%d-720.jpg" % (N))
im.thumbnail((120,120))
im.save("t-%d-120.jpg" % (N))
N=N+1
結果:27秒
方法7-並列PIL
これは、Pythonのマルチプロセッシングを使用する限り、Audionauticsの回答に対応することを目的としています。また、サムネイルのサイズごとに画像を再ロードする必要もなくなります。
#!/usr/local/bin/python3
import glob
from PIL import Image
from multiprocessing import Pool
def thumbnail(params):
filename, N = params
try:
# Load just once, then successively scale down
im=Image.open(filename)
im.thumbnail((1600,1600))
im.save("t-%d-1600.jpg" % (N))
im.thumbnail((720,720))
im.save("t-%d-720.jpg" % (N))
im.thumbnail((120,120))
im.save("t-%d-120.jpg" % (N))
return 'OK'
except Exception as e:
return e
files = glob.glob('image*.jpg')
pool = Pool(8)
results = pool.map(thumbnail, Zip(files,range((len(files)))))
結果:6秒
方法8-Parallel OpenCV
これは、OpenCVを使用している限り、bcattleの回答を改善することを目的としていますが、新しい解像度の出力をそれぞれ生成するためにイメージを再ロードする必要もありません。
#!/usr/local/bin/python3
import cv2
import glob
from multiprocessing import Pool
def thumbnail(params):
filename, N = params
try:
# Load just once, then successively scale down
im = cv2.imread(filename)
im = cv2.resize(im, (1600,1600))
cv2.imwrite("t-%d-1600.jpg" % N, im)
im = cv2.resize(im, (720,720))
cv2.imwrite("t-%d-720.jpg" % N, im)
im = cv2.resize(im, (120,120))
cv2.imwrite("t-%d-120.jpg" % N, im)
return 'OK'
except Exception as e:
return e
files = glob.glob('image*.jpg')
pool = Pool(8)
results = pool.map(thumbnail, Zip(files,range((len(files)))))
結果:5秒
質問には少し遅れます(たった1年です!)が、@ JakobBowyerの回答の「マルチプロセスit」の部分を基に説明します。
これは 恥ずかしいほど並列 の問題の良い例です。コードのメインビットはそれ自体の外部の状態を変更しないためです。単に入力を読み取り、その計算を実行して結果を保存します。
multiprocessing.Pool
が提供するマップ関数のおかげで、Pythonは実際にはこの種の問題にかなり優れています。
from PIL import Image
from multiprocessing import Pool
def thumbnail(image_details):
size, filename = image_details
try:
im = Image.open(filename)
im.thumbnail(size)
im.save("thumbnail_%s" % filename)
return 'OK'
except Exception as e:
return e
sizes = [(120,120), (720,720), (1600,1600)]
files = ['a.jpg','b.jpg','c.jpg']
pool = Pool(number_of_cores_to_use)
results = pool.map(thumbnail, Zip(sizes, files))
コードのコアは@JakobBowyerとまったく同じですが、シングルスレッドのループで実行する代わりに、マルチプロセッシングマップ関数を介して複数のコアに分散する関数にコードをラップしました。
別のオプションは python bindings to OpenCV を使用することです。これはPILやImagemagickよりも高速かもしれません。
import cv2
sizes = [(120, 120), (720, 720), (1600, 1600)]
image = cv2.imread("input.jpg")
for size in sizes:
resized_image = cv2.resize(image, size)
cv2.imwrite("thumbnail_%d.jpg" % size[0], resized_image)
より完全なウォークスルー here があります。
並行して実行したい場合は concurrent.futures
Py3の場合、またはPy2.7の futures
パッケージ:
import concurrent.futures
import cv2
def resize(input_filename, size):
image = cv2.imread(input_filename)
resized_image = cv2.resize(image, size)
cv2.imwrite("thumbnail_%s%d.jpg" % (input_filename.split('.')[0], size[0]), resized_image)
executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
sizes = [(120, 120), (720, 720), (1600, 1600)]
for size in sizes:
executor.submit(resize, "input.jpg", size)
すでにimagemagickを使い慣れている場合は、python-bindingsを使用してみませんか?
@ JakobBowyer & @ Audionautics に加えて、PIL
は非常に古く、トラブルシューティングを行って正しいバージョンを探すことができます...代わりに、 Pillow
from here ( source )
更新されたスニペットは次のようになります:
im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")
サムネイル作成のための完全な列挙スクリプト:
import os
from PIL import Image
output_dir = '.\\output'
thumbnail_size = (200,200)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for dirpath, dnames, fnames in os.walk(".\\input"):
for f in fnames:
full_path = os.path.join(dirpath, f)
if f.endswith(".jpg"):
filename = 'thubmnail_{0}'.format(f)
new_path = os.path.join(output_dir, filename)
if os.path.exists(new_path):
os.remove(new_path)
im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")
もう1つの答えは、品質について言及している人はいません(私はそう思いますか?)。
イーストロンドンのオリンピック公園でiPhone 6Sを使って撮った写真は次のとおりです。
屋根は木製のスラットのセットでできており、慎重に小型化しない限り、非常に厄介なモアレ効果が得られます。 stackoverflowにアップロードするには、画像をかなり重く圧縮する必要がありました---興味がある場合は オリジナルはここにあります 。
ここにcv2のサイズ変更があります:
$ python3
Python 3.7.3 (default, Apr 3 2019, 05:39:12)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> x = cv2.imread("IMG_1869.JPG")
>>> y = cv2.resize(x, (120, 90))
>>> cv2.imwrite("cv2.png", y)
True
これが vipsthumbnail です。
$ vipsthumbnail IMG_1869.JPG -s 120 -o vips.png
そして、左にvipsthnnailを付けて、横に並べてx2でズームした2つの縮小画像があります。
(ImageMagickはvipsthumbnailと同じ結果になります)
cv2のデフォルトはBILINEARなので、固定の2x2マスクがあります。出力画像のすべての点について、入力の対応する点を計算し、2x2の平均を取ります。つまり、実際には各ラインで最大240ポイントしかサンプリングせず、他の3750は単に無視しています。これは醜いエイリアシングを生成します。
vipsthumbnailは、より複雑な3ステージのダウンサイズを実行しています。
これは、完全なLanczos3カーネルと同等の品質を提供すると思われますが、ほとんどの方法でフィルターをボックス化できるため、より高速になります。
使用する必要があるライブラリを見つけようとしたとき、私は this に遭遇しました。
OpenCVはPILより明らかに速いようです。
とは言っても、私はスプレッドシートで作業しており、使用していたモジュール openpyxlは、画像を挿入するためにPILをインポートする必要があります であることがわかりました。