k5time
@k5time (k5 time)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

数10~数百枚のスクリーンショットを40枚単位で結合したい

とあるアプリのスクリーンショットを自動で定期取得しています。
日によって55枚だったり105枚だったり224枚だったりまちまちです。
iphoneで閲覧できるようにその日ごとに40枚単位で縦一列に結合した画像を
作りたいと思うのですがやり方がわかりません。

その日のスクリーンショットの数がN枚だった場合、
名前でソートされた各スクリーンショットを
「N / 40」 と余りがある場合+1 回(=M回)結合し保存するコードが構想できず困り果てています。

(1)N枚のスクリーンショットが格納されているフォルダから00-39までを結合し保存する
(2)「40-79」「80-119」・・・とM回繰り返す
(3)あまりの部分(224枚だった場合「200-223」)を結合し保存する

どなたかご教示いただけると幸甚に存じます。

全部一括で結合するコードは一応完成しており次の通りです。
```
files = glob.glob("*.png")

img_list = []
for n in range(len(files)):
img_list.append(cv2.imread(files[n]))

im_v = cv2.vconcat(img_list)
cv2.imwrite('■■.png',im_v)
```
これで出力した画像を「np.array_split」で分割しようとするとエラー(OutOfMemoryError)となります。
だいたい100枚を超えると駄目のようです。(結合後で30Mb程度、縦で10万ピクセル以上)
仕方ないので複数回に分けて結合しようと考えました。
その意味で「40枚+端数」は便宜的です。一定サイズ(10Mb程度、端数あり/無し)でもいいと思います。

以上、よろしくお願い致します。

0

6Answer

コード提供ありがとうございます。

  • (4)「80-85」の結合画像しか出力されていないようです ですが、 start_id、end_idを使って保存するファイル名作っていません。よって、全部xxx.pngに出力されます。それぞれのファイル名を作る必要があります。
-     # ここでいい具合にファイル名を作る
-     cv2.imwrite('xxx.png',im_v)
+     output_filename = "{}-{}.png".format(start_id,end_id)
+     cv2.imwrite(output_filename, im_v)
  • (5) インデントがおかしいです。コメントを抜くとこうなります。
コメントを除外
for n in range(files_num):
    img_list.append(cv2.imread(files[n]))
    if (n % catlimit == catlimit-1) or (n == files_num-1) :
        im_v = cv2.vconcat(img_list)
      start_id = ( (n - 1) // catlimit ) * catlimit
      end_id   = n
      cv2.imwrite('xxx.png',im_v)
      img_list = []

それぞれの行のindentを+1,+2と書くと、start_id以後が全部ズレています。

indentを追記
+0 : for n in range(files_num):
+1 :     img_list.append(cv2.imread(files[n]))
+1 :     if (n % catlimit == catlimit-1) or (n == files_num-1) :
+2 :         im_v = cv2.vconcat(img_list)
?? :      start_id = ( (n - 1) // catlimit ) * catlimit
?? :      end_id   = n
?? :      cv2.imwrite('xxx.png',im_v)
?? :      img_list = []

本来はこうすべきと思われます。ご確認下さい。

indentを修正
+0 : for n in range(files_num):
+1 :     img_list.append(cv2.imread(files[n]))
+1 :     if (n % catlimit == catlimit-1) or (n == files_num-1) :
+2 :         im_v = cv2.vconcat(img_list)
+2 :         start_id = ( (n - 1) // catlimit ) * catlimit
+2 :         end_id   = n
+2 :         cv2.imwrite('xxx.png',im_v)
+2 :         img_list = []

ここまでの話を聞いたまとめ

ファイル名生成をロジックに入れるならばこんな感じになるかな、と思われます。

sample
from PIL import Image
import glob
import os
import sys
import numpy as np
from natsort import natsorted
import cv2


#os.chdir(r'C:\■■■')  #86枚の画像があるフォルダです

files = glob.glob("*.png")
catlimit = 40
files_num = len(files)

img_list = []
for n in range(files_num):
    # 現在の画像を画像配列に追加
    img_list.append(cv2.imread(files[n]))

    # 画像配列を連結して出力する条件は (1)と(2)のいずれかを最低1つでも満たす事
    #  (1) nが catlimitで除した余りが、catlimit-1であること
    #  (2) nが 配列の最終要素であること

    if (n % catlimit == catlimit-1) or (n == files_num-1) :

        # 画像配列内の画像を連結して、出力画像を得る
        im_v = cv2.vconcat(img_list)

        # 出力ファイル名を生成するために、範囲を算出する
        # python3の場合"//"で切り捨てる除算となる
        # 例) catlimit = 40を前提条件とすれば
        #   n=79 の場合:( (79-1) // 40 ) * 40 = 1 * 40 = 40
        #   n=80 の場合:( (80-1) // 40 ) * 40 = 1 * 40 = 40
        #   n=81 の場合:( (81-1) // 40 ) * 40 = 2 * 40 = 80

        start_id = ( (n - 1) // catlimit ) * catlimit
        end_id   = n
        output_filename = "{}-{},png".format(start_id, end_id)

        # 出力画像を出力ファイル名のファイルへ書き出す
        cv2.imwrite(output_filename,im_v)

        # 次回に向けて画像配列は空にしておく
        img_list = []

余談

このソースコードでは、「入力画像ファイル(*.png)と出力画像ファイル(例えば、40-79,png)が同じフォルダに出力される」仕様になっています。そのため、同じフォルダで2回、3回実行すると、前回の出力画像ファイルが追加の入力画像ファイルと認識される事になりそうです。

1回目
 入力画像{"output1.png" "output2.png"} ⇒ 出力画像{"1-2.png"}
2回目
 入力画像{"output1.png" "output2.png","1-2,png" } ⇒ 出力画像{"1-3.png"}
3回目
入力画像{"output1.png" "output2.png","1-2,png", "1-3.png" } ⇒ 出力画像{"1-4.png"}

これが望ましくない振る舞いであれば、適当な条件をご検討下さい。

  • 入出力ごとにフォルダを分ける
    • 入力画像は、inputフォルダ
    • 出力画像は、Outputフォルダ
  • 入力ファイル名に条件をつける
    • 入力画像は、"input*.png"
    • 出力画像は、"output.png"
  • 出力ファイルをJPEG形式にする
    • 入力画像は、"*.png"
    • 出力画像は、"*.jpg"
2Like

Comments

  1. @k5time

    Questioner

    ご丁寧にありがとうございます。
    無事、250枚超の画像の処理に成功したことを確認しました。

    インデント表記に不備があり失礼いたしました。
    ご指摘の通り、出力画像のファイル名を都度変更していなかったので
    最後の出力画像以外が上書きされてしまっていました。
    画像の出力形式・出力先の指摘も的確です。
    出力先は当方のiphoneで共有設定しているgoogleドライブに変更しました。

    検証や記法など、コードの知識以外にも自分にとって有益な知見がありました。
    この度は誠にありがとうございました。

pythonあまり使ったことがないの疑似コード(動かないコード)ですが。

多分こういう考え方をすればできないこともないと思われます。

files = glob.glob("*.png")
img_list = []
catlimit = 40
files_num = len(files)

for n in range(files_num):
    # 現在の画像を配列に追加
    img_list.append(cv2.imread(files[n]))

    # 配列上の画像を出力する条件は (1)と(2)のいずれかを最低1つでも満たす事
    #  (1) nが catlimitで除した余りが、catlimit-1であること
    #  (2) nが 配列の最終要素であること
    if (n % catlimit == catlimit-1) or (n == files_num-1) :

      im_v = cv2.vconcat(img_list)

      # python3の場合"//"で切り捨てになる(らしい)
      # 例) catlimit = 40の場合
      #   n=79 の場合:( (79-1) // 40 ) * 40 = 1 * 40 = 40
      #   n=80 の場合:( (80-1) // 40 ) * 40 = 1 * 40 = 40
      #   n=81 の場合:( (81-1) // 40 ) * 40 = 2 * 40 = 80

      start_id = ( (n - 1) // catlimit ) * catlimit
      end_id   = n

      # ここでいい具合にファイル名を作る
      cv2.imwrite('■■.png',im_v)

      # 次回に向けて空にしておく
      img_list = []

0Like

ありがとうございます。
早速全86枚の画像で試してみましたが、
「80-85」の結合画像しか出力されていないようです

擬似コードを参考に前進していきたいと思います
他にアドバイスがあれば、ご指導お願いします

0Like
  • (1) まず動いていないといっているソースコードそのものが必要です。
    • 手元にないものについてコメントはできません。
  • (2) imwriteが何回、なんというファイル名で呼ばれたのか、ログを仕込んで確認して下さい。
    • 同じファイル名で何回も呼ばれ、上書きしているのか?
      • ⇒ ファイル名を生成するロジックに問題があるのか?
    • 1回しか呼ばれていないのか?
      • ⇒ ファイルを書きだすタイミングの問題か?
      • 例えば、下記コードのTEST4のように、最後に1回しか書き込まない状況になっていませんか?
  • その他 : 質問のコードにも全くインデントがないように見えています。Markdown記法 チートシート-Code - コードの挿入をご確認し、インデントが見えるように記載してください。
test.py
files_num = 86
catlimit = 40

print ("[[ TEST1 ]] ")
img_list = []
for i in range(files_num):
    img_list.append(i)
print ( " " , img_list )

print ("[[ TEST2 ]] ")
img_list = []
for i in range(files_num):
    img_list.append(i)
    if i % catlimit == catlimit - 1:
        start = ( i - 1 ) // catlimit * catlimit
        end = i
        print (" {}-{}:{}".format(start , end, img_list) )
        img_list = []

print ("[[ TEST3 ]] ")
img_list = []
for i in range(files_num):
    img_list.append(i)
    if (i % catlimit == catlimit - 1) or ( i == files_num - 1 ) :
        start = ( i - 1 ) // catlimit * catlimit
        end = i
        print (" {}-{}:{}".format(start , end, img_list) )
        img_list = []


print ("[[ TEST4 ]] ")
img_list = []
for i in range(files_num):
    img_list.append(i)
    if (i % catlimit == catlimit - 1) or ( i == files_num - 1 ) :
        start = ( i - 1 ) // catlimit * catlimit
        end = i
print (" {}-{}:{}".format(start , end, img_list) )
img_list = []
$ python3 --version
Python 3.9.7

$ python3 test.py
[[ TEST1 ]]
  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85]
[[ TEST2 ]]
 0-39:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
 40-79:[40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[[ TEST3 ]]
 0-39:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
 40-79:[40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
 80-85:[80, 81, 82, 83, 84, 85]
[[ TEST4 ]]
 80-85:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85]
0Like

コメントありがとうございます。
不勉強で申し訳ございません。
ひとまず、動かしてみたコードをMarkdown記法で共有します。

qiita.py

from PIL import Image
import glob
import os
import sys
import numpy as np
from natsort import natsorted
import cv2


#os.chdir(r'C:\■■■')  #86枚の画像があるフォルダです

files = glob.glob("*.png")
img_list = []
catlimit = 40
files_num = len(files)

for n in range(files_num):
    # 現在の画像を配列に追加
    img_list.append(cv2.imread(files[n]))

    # 配列上の画像を出力する条件は (1)と(2)のいずれかを最低1つでも満たす事
    #  (1) nが catlimitで除した余りが、catlimit-1であること
    #  (2) nが 配列の最終要素であること
    if (n % catlimit == catlimit-1) or (n == files_num-1) :

        im_v = cv2.vconcat(img_list)

      # python3の場合"//"で切り捨てになる(らしい)
      # 例) catlimit = 40の場合
      #   n=79 の場合:( (79-1) // 40 ) * 40 = 1 * 40 = 40
      #   n=80 の場合:( (80-1) // 40 ) * 40 = 1 * 40 = 40
      #   n=81 の場合:( (81-1) // 40 ) * 40 = 2 * 40 = 80

      start_id = ( (n - 1) // catlimit ) * catlimit
      end_id   = n

      # ここでいい具合にファイル名を作る
      cv2.imwrite('xxx.png',im_v)

      # 次回に向けて空にしておく
      img_list = []

インデントは半角スペース4つで表現しています。
ログを仕込んで原因の究明を行います。
引き続き、よろしくお願い致します。

0Like

chunks() 関数を用意してリストを40個ずつ分割してイテレーションすると楽だと思います。

# 参考 https://stackoverflow.com/a/312464/454997
>>> def chunks(lst, n):
...     return [lst[i:i + n] for i in range(0, len(lst), n)]
...
>>> chunks([1, 2, 3, 4, 5, 6, 7, 8], 3)
[[1, 2, 3], [4, 5, 6], [7, 8]]

chunks() はこのように与えられたリストを n 個ずつのリストに分割します。余りが出たら最後のリストは短くなります。

これを使うと:

def chunks(lst, n):
    return [lst[i:i + n] for i in range(0, len(lst), n)]

files = glob.glob("*.png")
chunk_size = 40

for i, chunk in enumerate(chunks(files, chunk_size)):
    # ここで chunk は40個のファイル名が入ったリスト。
    # 余りが出たら最後の chunk は40個未満になる。
    img_list = [cv2.imread(f) for f in chunk]
    im_v = cv2.vconcat(img_list)

    # ファイル名は @hon_no_mushi さんの回答に合わせて
    # 0-39.png, 40-79.png... となるようにした。
    output_filename =  f"{i * chunk_size}-{(i + 1) * chunk_size - 1}.png"

    cv2.imwrite(output_filename, im_v)
0Like

Comments

  1. @k5time

    Questioner

    ご回答ありがとうございました。
    バッチリ動作しているようです。

Your answer might help someone💌