印刷物からスキャンした画像の網点除去

古い写真のデジタル化を始めたのですが、卒業アルバムなどオフセット印刷による印刷物は細かい網状の点で色を表現していて、そのままスキャンすると撮像素子やディスプレイの解像度との関係でモアレ縞が発生したりして美しくありません。フォトショップなどの市販ソフトで網点の除去処理ができるようですが、PythonOpenCVライブラリを使って市販ソフトと同じような処理ができないか試してみました。

import os
import cv2
import glob
import numpy as np
import PIL
from scipy.ndimage.filters import median_filter
from tqdm import tqdm
def cv2pil(image):
    new_image = image.copy()
    if new_image.ndim == 2:
        pass
    elif new_image.shape[2] == 3:
        new_image = cv2.cvtColor(new_image, cv2.COLOR_BGR2RGB)
    elif new_image.shape[2] == 4:
        new_image = cv2.cvtColor(new_image, cv2.COLOR_BGRA2RGBA)
    new_image = PIL.Image.fromarray(new_image)
    return new_image
def unsharp(image, sigma, strength):
    # Median filtering
    image_mf = median_filter(image, sigma)
    # Calculate the Laplacian
    lap = cv2.Laplacian(image_mf,cv2.CV_64F)
    # Calculate the sharpened image
    sharp = image - strength * lap
    # Saturate the pixels in either direction
    sharp[sharp>255] = 255
    sharp[sharp<0] = 0
    
    return sharp
files = sorted(glob.glob('tmp/20*.jpg'))
img = cv2.imread(files[1])
height, width, c = img.shape
print(width, height)
10800 7200

解像度600dpiでスキャンした卒業アルバムです。サイズが大きすぎるので小さく切り取ってテストします。

x0 = 3400
y0 = 3500
w = 800
h = int(w / 16 * 9)
x1 = min(x0 + w, width)
y1 = min(y0 + h, height)
testimg = img[y0:y1, x0:x1, :]
cv2pil(testimg)

f:id:lathe00744:20201122203647p:plain

ガウシアンフィルタでぼかして網点を平滑化してみます。

blimg = cv2.GaussianBlur(testimg, (3, 3), 0)
cv2pil(blimg)

f:id:lathe00744:20201122203925p:plain

非局所平均フィルタという処理で、ノイズを除去します。

nlmimg = cv2.fastNlMeansDenoising(blimg, None, 7, 7, 21)
cv2pil(nlmimg)

f:id:lathe00744:20201122204002p:plain

ボケた画像にエッジ強調をかけます。

shimg = np.zeros_like(nlmimg)
for i in range(3):
    shimg[:,:,i] = unsharp(nlmimg[:,:,i], 4, 1.0)
cv2pil(shimg)

f:id:lathe00744:20201122204040p:plain

時計の3時から6時あたりの目盛が消えかかってしまいましたが、まあこんなもんでしょうか。 以下、複数の画像を全部処理します。Core i7 8700のPCで1枚あたり1分以上かかる重たい処理です。

def process_img(img):
    blimg = cv2.GaussianBlur(img, (3, 3), 0)
    nlmimg = cv2.fastNlMeansDenoising(blimg, None, 7, 7, 21)
    shimg = np.zeros_like(nlmimg)
    for i in range(3):
        shimg[:,:,i] = unsharp(nlmimg[:,:,i], 4, 1.0)
    return shimg
folder = 'test'
if not os.path.exists(folder):
    os.mkdir(folder)
for fpath in tqdm(files):
    img = cv2.imread(fpath)
    dst = process_img(img)
    fdst = os.path.join(folder, os.path.basename(fpath))
    cv2.imwrite(fdst, dst)
100%|██████████| 33/33 [33:46<00:00, 61.40s/it]

他の処理例 f:id:lathe00744:20201123102857p:plain

f:id:lathe00744:20201123102917p:plain