yiskw note

機械学習やプログラミングについて気まぐれで書きます

【Python】画像スクランブルを実装する


概要

画像のピクセルを並び替えることで,視覚的に認識ができない画像に変換する画像スクランブルについて調査してみました.
またPythonを用いて画像スクランブル,及び画像の復元まで実装してみたので,こちらにメモを残しておきます.
実装はこちらにて公開しております.

画像スクランブルとは

f:id:yiskw713:20211230202049j:plain
※猫の画像は K LによるPixabayのものを使用

こちらの論文によると

Image scrambling is the method of rearranging the pixels randomly to make the image visually unreadable and break the correlation between the neighboring pixels.

すなわち,以下の図のように,画像のピクセルを並び替えて,視覚的に理解できないような画像にする技術のことのようです.
この際に,元画像とスクランブル後の画像の二つの間で,並び替え方が不変となるような方法を用いることで,画像の復元も可能です.
今回は画像のRGBの平均画素値のハッシュ値をシードとして並び替えることで,画像の復元を可能にしています.
このスクランブルを画像全体ではなく,画像のパッチごとに実施する,block-wise image scramblingというものもあるようです.(参考論文)

今回実装する画像スクランブルの大まかな流れ

  1. 画像内のRGB画素値のそれぞれの平均を計算する
  2. この平均値を用いて,ハッシュ値を生成する
  3. ハッシュ値でシードを固定する
  4. 固定したシードを用いて,ピクセルの順番をランダムに並び替える

画像内のRGB値の平均値からハッシュ値を計算することで,ピクセルを並び替える前後でハッシュ値が一意になります.
このハッシュ値をシードにしてピクセルの並び替えを行うので,スクランブル画像を元のピクセルの順に並び替えることで画像の復元も可能です.

Pythonで実装する

こちらの実装を参考に実装しました.
参考にした実装ではfor文が使用されていましたが,numpy配列で扱うことで,そのあたりの計算量を減らしています.

import numpy as np
from PIL import Image
import random
from typing import List


class ImageScramble(object):
    """Image Scrambling using Pillow Image.
    Reference:
    https://stackoverflow.com/questions/17777760/mixing-pixels-of-an-image-manually-using-python
    """

    @staticmethod
    def _set_seed_from_image(img: np.ndarray) -> None:
        """画像からハッシュ値を計算し,シードを設定する.
        ハッシュ値は,画像スクランブルの前後で変わってはいけない.
        今回はRGBそれぞれの画素値の平均からハッシュ値を算出した.
        Args:
            img (np.ndarray): input image
        """
        # use RGB mean value to calculate hash value
        average = np.mean(img.reshape(-1, 3), axis=0)
        average = tuple(average)
        random.seed(hash(average))

    @staticmethod
    def _get_pixels(img: np.ndarray) -> np.ndarray:
        """Get the list of pixel values
        Args:
            img (np.ndarray): (h, w, 3)
        Returns:
            np.ndarray: (h*w, 3)
        """
        pixels = img.reshape(-1, 3)
        return pixels

    @staticmethod
    def _restore_image(
        pixels: np.ndarray, original_width: int, original_height: int
    ) -> np.ndarray:
        """Restore an image from the list of pixel values
        Args:
            pixels (np.ndarray): (h*w, 3)
            original_width (int): the width of an original image
            original_height (int): the height of an original image
        Returns:
            np.ndarray: (h, w, 3)
        """
        img = pixels.reshape((original_height, original_width, 3))
        return img

    @staticmethod
    def _scramble_index(pixels: np.ndarray) -> List[int]:
        """Scramble indicies of pixels.
        Args:
            pixels (np.ndarray): (h*w, 3)
        Returns:
            List[int]: shuffled index list
        """
        idx = list(range(len(pixels)))
        random.shuffle(idx)
        return idx

    def _scramble_pixels(self, img: np.ndarray) -> np.ndarray:
        """Scramble pixels
        Args:
            img (np.ndarray): (h, w, 3)
        Returns:
            np.ndarray: (h*w, 3)
        """
        pixels = self._get_pixels(img)
        idx = self._scramble_index(pixels)

        scrambled_pixels = pixels[idx]
        return scrambled_pixels

    def scramble(self, img: Image.Image) -> Image.Image:
        """Do image scrambling.
        Args:
            img (Image.Image): input image
        Returns:
            Image.Image: scrambled image
        """
        w, h = img.size

        img = np.asarray(img)

        self._set_seed_from_image(img)
        scrambled_pixels = self._scramble_pixels(img)
        img = self._restore_image(scrambled_pixels, w, h)
        return Image.fromarray(img)

    def _unscramble_pixels(self, img: np.ndarray) -> np.ndarray:
        """Unscramble pixels
        Args:
            img (np.ndarray): (h, w, 3)
        Returns:
            np.ndarray: (h*w, 3)
        """
        pixels = self._get_pixels(img)

        idx = self._scramble_index(pixels)
        # argsortを使って元画像のピクセルのインデックスを取得する
        idx = np.argsort(idx)

        original_pixels = pixels[idx]
        return original_pixels

    def unscramble(self, img: Image.Image) -> Image.Image:
        """Do unscramgling image
        Args:
            img (Image.Image): input scrambled image.
        Returns:
            Image.Image: unscrambled image.
        """
        w, h = img.size

        img = np.asarray(img)

        self._set_seed_from_image(img)
        original_pixels = self._unscramble_pixels(img)
        img = self._restore_image(original_pixels, w, h)
        return Image.fromarray(img)

実際に使ってみた結果

f:id:yiskw713:20211228160752j:plain:w800
Szabolcs MolnarによるPixabayからの画像

こちらの画像を使用して画像スクランブルを実装してみます.

img = Image.open("./dog.jpeg")

processor = ImageScramble()
scrambled = processor.scramble(img)
scrambled.save("scramble.png")

unscrambled = processor.unscramble(scrambled)
unscrambled.save("unscramble.png")

画像スクランブルを実行した結果(scramble.png)が以下です.

f:id:yiskw713:20211229120253p:plain:w800

しっかりとピクセルの順番が代わり,視覚的に認識できない画像になりました.
続いて,スクランブル画像から元の画像を復元した結果(unscramble.png)が以下です.

f:id:yiskw713:20211229120319p:plain:w800

しっかりと復元できていました!

まとめ

画像のピクセルの順番を並び替えて,視覚的に認識できないようにする画像スクランブルを実装してみました.
スクランブルされた画像に対して画像認識を試みる研究もあるようなので,今後試してみたいと思います.

参考