Python-OpenCVで画像の拡大でOutOfMemoryErrorが発生してメインメモリーの増設したいと思った話

コンピュータ

メインメモリーを64GB搭載しているPCで画像ファイルを拡大処理をしていたところOutOfMemoryErrorが発生しました。
元が1000×1000ぐらいの解像度の画像を256倍は通りましたが、300倍ではエラーになります。

そんなに大きい画像を作る理由はさておき、処理を実行するとCPUに使用率は最大では無いですがかなり高いレベル、メインメモリーは最大で張り付き、ページファイルの書き出しでSSDの使用率も跳ね上がります。冷却ファンがうなりを上げて仕事をしている雰囲気を醸し出します。しばらくするとPCを設置している足元で廃棄熱を感じるようになります。
Windows事態が半分フリーズ状態で、マウスの操作が極端に遅く場合によっては反応しないこともあります。
昔のPCを見ているようで懐かしいのですが、昔と違う点は高負荷をかけてもしっかり終了すると言うことです。

閑話休題

メインメモリーを使うアプリケーションを終了するなどしてメモリの空き容量を増やした状態で実行したところ、拡大倍率を増やすことが出来ました。また、メモリ容量の少ないPCで実行したところより小さな拡大倍率でエラーが発生していました。

ということはメインメモリー物理的容量を増やすことでOutOfMemoryErrorを解消することが出来そうです。今使っているPCのメモリーはDDR4が4スロットで、16GBのメモリモジュールをx4枚積んでいます。マザーボードのカタログを見ると合計128GBまで搭載できるようなので、32GBのメモリモジュールを4枚確保できれば倍の容量にすることが可能です。


今amazonで価格を見ると32GBx2枚組が2万円前後、それを2セットいった感じになります。

出せる金額かどうかはさておき、それしか方法が無いのであれば購入する理由にはなります。

とまぁ、そのような心理状態に自分を追い込んでみましたが、実行しようとしていた処理は拡大後縮小するので、メモリを増やさなくとも画像を分割してあげれば解決する問題だと気が付きました。

せっかくのメモリー増設の機会が失われたのは残念ですが、出費が減ったことは喜ばしいと思います。

import cv2
import numpy as np
import os, glob

def filter(img, scale):
    img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)
    return cv2.resize(img, None, fx=1.0/scale, fy=1.0/scale, interpolation=cv2.INTER_NEAREST)

if __name__ == '__main__':
    src_dir = './intputdir'
    dst_dir = './outputdir'
    scale = 13
    split_size = 512
    split_width = split_size
    split_height = split_size
    os.makedirs(dst_dir, exist_ok=True)
    for src_file in glob.glob((src_dir+'/005.jpg')):
        basename = os.path.splitext(os.path.basename(src_file))[0]
        
        gray = cv2.imread(src_file,0)
        (h, w) = gray.shape[:2]

        w_n = w // split_width
        if ((w % split_width) > 0):
            w_n = w_n + 1
        
        h_n = h // split_height
        if ((h % split_height) > 0):
            h_n = h_n + 1

        scale = 256
        nm = 1
        dst = gray.copy()
        for n in range(nm):
            for y in range(h_n):
                for x in range(w_n):
                    print("n:"+str(n)+"y:"+str(y)+"x:"+str(x))
                    yy = y*split_height
                    xx = x*split_width
                    height = split_height
                    if (yy+height) > h:
                        height = h - yy
                    width = split_width
                    if (xx+width) > w:
                        width = w - xx
                    tmp = dst[yy:yy+height, xx:xx+width]
                    tmp2 = filter(tmp, scale)
                    dst[yy:yy+height, xx:xx+width] = tmp2
        dst = cv2.GaussianBlur(dst, (3,3), 0)
        dst = cv2.fastNlMeansDenoising(dst, h=15)
        dst_file = os.path.join(dst_dir, (basename+"LN"+str(split_size)+"x"+str(scale)+"n"+str(nm)+"g3h15.png"))
        cv2.imwrite(dst_file, dst)
#    dst = cv2.bilateralFilter(gray, 3, sigmaColor=999, sigmaSpace=4) # 1034
#    dst = cv2.fastNlMeansDenoising(dst, h=7) # 903

## 拡大縮小は77倍これ以上はファイルサイズが小さくならない=同じような絵
## 2回3回と繰り返すと、線の凸凹が目立つ
## GaussianBlur 線の凸凹が目立つ
## fastNlMeansDenoising 若干良いか?

追記:20240509
仮想メモリについて物理メモリが不足すると利用されるストレージ(HDD or SSD)上のファイルだと思っていました。

たまに、ストレージはメモリと比べて遅いので仮想メモリを使うとパフォーマンスの低下する原因になります。沢山メモリを積んだPCではページファイルのサイズを0に設定し仮想メモリをOFFすることによりパフォーマンスが向上する的な記事を目にすることがありました。

今回メモリ不足になったことで、仮想メモリのページファイルのサイズを大きな値に変更してみましたが、メモリの上限が変わることは無さそうでした。メモリ不足になった際、他のアプリケーションなどで確保されているメモリの内容をストレージに一時的に退避することでメインメモリに空き領域を作る仕組みだと考えられます。その退避先がページファイルだと思われます。

そうなりますと仮想メモリをOFF(ページファイルサイズを0)にするとメモリ不足になると即エラーが発生すると思われますので、普通に使う分にはページファイルは設定しておいた方が良さそうな感じがします。

逆に考えるとページファイルサイズを0に設定でメモリ不足が発生するのであれば、物理メモリを増設せざるを得ない状況になりますので、読者のメモリ購入意欲を高める販促策の一環なのかもしれませン。

追記2:20240509
pythonで画像を分割しながら拡大縮小する(誰が使うか謎な)スクリプトを作りましたが、結合した際合わせ目の部分が不自然な感じになりました。やっぱりメモリは沢山欲しいですね。

コメント