VSS (Visual Secret Sharing)の実装と出力（OHP＆レーザー彫刻）

# はじめに

2 out of 2 VSSのプログラム例と出力例（OHP＆レーザー彫刻）について紹介します。
VSSはVisual Secret Sharingの略で、日本語にすれば視覚秘密分散です。
2 out of 2は２枚のノイズに見える画像を重ねることで、１つの意味ある画像が見えることを意味します。
他に、３枚の画像のうち全部揃わないと見えないとか、２枚揃えば見えるとか、複数のバリエーションがあります。

# 環境
Processing 4
xtools Creative Space
レーザープリンタ
A4 OHPシート

# 手順

まず、適当に画像を用意します。
ここでは、
・サイズ：100x20ピクセル
・白地に黒文字
・２値化画像
を用意しました。
２値化はプログラム内でやってもいいと思いますが、念の為、手作業で。
![Hextomino.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/c4d58c47-a1fa-494a-adb0-c26ecda23eb9.png)

これを下記プログラムに通します。

```java:Processing
void setup() {
  size(800, 600);
  background(0);

  PImage in = loadImage("Hextomino.png");// 入力画像

  int blockSize = 2; //各ピクセルを 2x2 に拡大してパターンを埋め込む
  PImage[] out = generateVSSout(in, blockSize);

  //２画像の保存
  out[0].save("out1.png");
  out[1].save("out2.png");

  // 表示（４倍に拡大）
  scale(4, 4);
  noSmooth();
  image(out[0], 0, 0);
  image(out[1], 0, 50);

  image(out[0], 0, 100);
  blendMode(ADD);
  image(out[1], 0, 100);
  
  // 保存（４倍に拡大）
  PImage i = get(0, 0, in.width * 2 * 4, in.height * 2 * 4);
  PImage j = get(0, 50*4, in.width * 2 * 4, in.height * 2 * 4);
  PImage k = get(0, 100*4, in.width * 2 * 4, in.height * 2 * 4);
  i.save("out1x4.png");
  j.save("out2x4.png");
  k.save("blend_x4.png");
}

/**
 * 2値の視覚秘密分散(2-out-of-2)を行う関数。
 * 入力画像in（白背景に黒文字）を読み取り、2つのシェア(PImage)を返す。
 * blockSizeは1画素を何倍に拡大してパターンを割り当てるかの指定。
 */
PImage[] generateVSSout(PImage img, int blockSize) {

  // 元画像の幅・高さ
  int w = img.width;
  int h = img.height;

  img.loadPixels();

  // シェア画像は (w * blockSize, h * blockSize) サイズ
  PImage s1 = createImage(w * blockSize, h * blockSize, RGB);
  PImage s2 = createImage(w * blockSize, h * blockSize, RGB);
  s1.loadPixels();
  s2.loadPixels();

  // 2x2パターン用の座標定義（例）
  //   黒画素のとき: 同パターン(A,A or B,B) → 重なると真っ黒
  //   白画素のとき: 異なるパターン(A,B) → 重なりがずれて白っぽく見える
  int[][] patternA = { {0, 0}, {1, 1} };
  int[][] patternB = { {0, 1}, {1, 0} };

  // 全ピクセルを白にする
  for (int i = 0; i < s1.pixels.length; i++) {
    s1.pixels[i] = color(255);
    s2.pixels[i] = color(255);
  }

  for (int y = 0; y < h; y++) {
    for (int x = 0; x < w; x++) {
      // in 画像の (x, y) のピクセルの赤成分を取得
      float r = red(img.pixels[y * w + x]);

      // r < 128 を「黒画素」とみなす（適宜調整）
      boolean isBlack = (r < 128);

      int[][] p1, p2;
      if (isBlack) {
        // 黒画素 → 同じパターンを両方のシェアに
        if (random(1) < 0.5) {
          p1 = patternA;
          p2 = patternA;
        } else {
          p1 = patternB;
          p2 = patternB;
        }
      } else {
        // 白画素 → 異なるパターンをシェアに
        if (random(1) < 0.5) {
          p1 = patternA;
          p2 = patternB;
        } else {
          p1 = patternB;
          p2 = patternA;
        }
      }

      // out画像に書き込む先頭ブロック座標
      int bx = x * blockSize;
      int by = y * blockSize;

      // s1 にパターン p1 を書き込み
      for (int[] coord : p1) {
        int dx = coord[0];
        int dy = coord[1];
        int index = (by + dy) * s1.width + (bx + dx);
        s1.pixels[index] = color(0); // 黒
      }
      // s2 にパターン p2 を書き込み
      for (int[] coord : p2) {
        int dx = coord[0];
        int dy = coord[1];
        int index = (by + dy) * s2.width + (bx + dx);
        s2.pixels[index] = color(0); // 黒
      }
    }
  }


  s1.updatePixels();
  s2.updatePixels();

  return new PImage[] { s1, s2 };
}
```
# 出力結果
乱数要素があるので、あくまで例です。
実行すると、まず、入力画像の縦横２倍(200x40)の画像が２種類出力されます。
![out1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/02eaa39a-5cfd-49ce-ac5b-c716ef6defa1.png)
![out2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/a0f548de-f3ff-40a7-ab71-b52e6c3d425f.png)

これは、１ピクセル単位で、なかなか扱いが不便なこともあるので、
エッジをぼかさずに４倍して出力し直します。
（Qiitaの仕組み上、この記事の画像ではボケてるかもしれません）
![out1x4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/abb146c2-8345-4421-a42a-06002053e19b.png)
![out2x4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/557ce854-3e79-408f-9f0a-4e5ce1203160.png)

この２画像を重ねると（プログラム上ではBLEND ADD）元画像が見えてきます。
![blend_x4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/275099ed-70fe-47f9-b162-33f3827e5b3d.png)

# レーザー彫刻
使用機材は、
xTool D1Pro 10W
素材はダイソーの黒い名刺ケース。
画像を読み込んで、レーザーでアルミの黒塗装をはがしてくれる。
名刺サイズで30~40分。遅！

【設定】
「out1x4.png」は白黒反転させずに、デフォルトのまま。
画像の黒部分が、レーザー発振して、アルミ塗装がはがれるので、
結果的に、データの黒がシルバーに、データの白がブラックになります。
・幅を910mmに。
・素材をMetal Cardにした。
・ビットマップモードをSierraに変更。
・彫刻密度はMaxの260に変更。

ビットマップモードについてはよくわかりませんが、設定後にシミュレーション画像が出て、一番マシに感じました。色勾配のある部分では、自動的にレーザーの線が入るようなのですが、これを無効にできませんでした＆調べる時間がありませんでした。

※印刷が途中で止まりました。原因不明です。もしかしたら、このような模様（空間周波数が高い）は、想定外で、エッジのトレースとかで、メモリがパンクしているのかもしれません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/040f8426-35ec-4b09-9146-543e347434a2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/86d87b21-68fa-4ef0-b68b-44f0b17d5e00.png)

# OHP印刷

「out2x4.png」を幅を910mmでOHPに印刷します。
白は、透明になり、
黒は、黒になります。

理論的には、これでうまくいくはずですが、OHPシートがレーザープリンタの熱で、若干、伸縮します。
なので、91.1mmとか、若干伸ばしたほうが、いいかもしれません。

# 結果
やってみないと、わからないですが、２枚をピッタリ重ねるのは難しいです。
コンピュータなら楽ですが、
物理的に重ねる場合、回転も合わせる必要があるので、想像以上にシビアです。
合わせ方が悪いのか、印刷の大きさが悪いのか、ある程度、試行錯誤が必要です。
OHPシートもったいないので、スケール違いを複数並べて、いっぺんに印刷して、合成がいい感じのものだけを選抜したほうが、楽です。

OHPとOHPなら、伸縮率同じだから、もう少し楽でしょう。

# 用語
| 日本語 | 英語 | 略 |
|:-:|:-:|:-:|
|視覚暗号|Visual Cryptography|VC|
|視覚暗号法|Visual Cryptography Scheme|VCS|
|(カラー白黒VSC)|Colored Black and White VCS|CBW-VCS|
|(視覚秘密分散)|Visual Secret Sharing|VSS|
|-|Progressive Visual Secret Sharing|PVSS|
|視覚復号型秘密分散法|Visual Secret Sharing Scheme|VSSS|

日本語ではVSSSがHitしますが、英語文献ではVSSが一般的と思います。

# 参考サイト

https://en.wikipedia.org/wiki/Visual_cryptography

https://acompany.tech/privacytechlab/Visual-Secret-Sharing-Scheme

https://slitanimation.com/2020/05/secret/

https://www.vector.co.jp/soft/winnt/util/se483505.html

Youtube「視覚復号型秘密分散」を使って、色んなネタを作ってみた

https://www.youtube.com/watch?v=uUkVOIpL25w&t=50s

Contrast-Enhanced Color Visual Cryptography for (k, n) Threshold Schemes

https://dl.acm.org/doi/10.1145/3508394

岩本・渡邉研究室の解説ページ

https://www.iw-lab.jp/users/mitsugu/research/VSSS/main.html

https://qiita.com/SiZK/items/ca4b246e47d99dfe585c

https://qiita.com/SiZK/items/854ff9bf387b411e8389

マウス操作で体験できる

https://www.101computing.net/visual-cryptography/

入力画像から、音声の周波数分析結果が元の画像になるような音声生成が面白い。

https://en.wikipedia.org/wiki/Steganography

https://qiita.com/hextomino/items/3bfa49b3f4f31cc7e133