web-dev-qa-db-ja.com

Pythonで画像のサムネイルを生成する最も速い方法は何ですか?

私はPythonでフォトギャラリーを構築しており、高解像度画像のサムネイルをすばやく生成できるようにしたいと考えています。

さまざまな画像ソースの高品質サムネイルを生成する最も速い方法は何ですか?

Imagemagickなどの外部ライブラリを使用する必要がありますか、またはこれを行う効率的な内部方法はありますか?

サイズ変更された画像のサイズは(最大サイズ)になります。

120x120
720x720
1600x1600

元の色をできるだけ多く保持し、圧縮によるアーティファクトを最小限に抑えたいので、品質が問題になります。

ありがとう。

27
ensnare

あなたはそれが簡単にこれを行う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)))

必死にスピードが必要なら。次に、スレッド化、マルチプロセス、または別の言語を取得します。

24
Jakob Bowyer

私は楽しいと思ったので、上記で提案されたさまざまな方法といくつかの自分のアイデアについてベンチマークを行いました。

私は、それぞれ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秒

18
Mark Setchell

質問には少し遅れます(たった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とまったく同じですが、シングルスレッドのループで実行する代わりに、マルチプロセッシングマップ関数を介して複数のコアに分散する関数にコードをラップしました。

12
Audionautics

別のオプションは 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)
4
bcattle

すでにimagemagickを使い慣れている場合は、python-bindingsを使用してみませんか?

PythonMagick

3
Don Question

Python 2.7、Windows、x64ユーザー

@ JakobBowyer@ Audionautics に加えて、PILは非常に古く、トラブルシューティングを行って正しいバージョンを探すことができます...代わりに、 Pillow from heresource

更新されたスニペットは次のようになります:

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")
2
Jossef Harush

もう1つの答えは、品質について言及している人はいません(私はそう思いますか?)。

イーストロンドンのオリンピック公園でiPhone 6Sを使って撮った写真は次のとおりです。

roof of olympic swimming pool

屋根は木製のスラットのセットでできており、慎重に小型化しない限り、非常に厄介なモアレ効果が得られます。 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つの縮小画像があります。

results of downsize to 120 pixels across

(ImageMagickはvipsthumbnailと同じ結果になります)

cv2のデフォルトはBILINEARなので、固定の2x2マスクがあります。出力画像のすべての点について、入力の対応する点を計算し、2x2の平均を取ります。つまり、実際には各ラインで最大240ポイントしかサンプリングせず、他の3750は単に無視しています。これは醜いエイリアシングを生成します。

vipsthumbnailは、より複雑な3ステージのダウンサイズを実行しています。

  1. Libjpegのシュリンクオンロード機能を使用して、ボックスフィルターで画像を各軸で8倍に縮小し、画像全体の4032ピクセルを504 x 378ピクセルに変換します。
  2. さらに2 x 2のボックスフィルターを縮小して、252 x 189ピクセルを取得します。
  3. 5 x 5 Lanczos3カーネルで終了し、120 x 90ピクセルの画像を出力します。

これは、完全なLanczos3カーネルと同等の品質を提供すると思われますが、ほとんどの方法でフィルターをボックス化できるため、より高速になります。

2
jcupitt

使用する必要があるライブラリを見つけようとしたとき、私は this に遭遇しました。

OpenCVはPILより明らかに速いようです。

とは言っても、私はスプレッドシートで作業しており、使用していたモジュール openpyxlは、画像を挿入するためにPILをインポートする必要があります であることがわかりました。

0
virtualxtc