見出し画像

#36 おしゃれインスタグラマーみたいな画像作りたい!!!!

どうも、まりなです。
実は少し前からインスタやってます。

でも全然フォロワーが増えません。まだ0フォローです。なんでだ…

私のインスタには何かが足りないのでしょうか…そう思いながら過去の投稿を見返して気付きました。
そうか、あれだ。

引用: https://www.excite.co.jp/news/article/ExApptopi_179804/image/3/

「白黒で一部だけカラーのオシャレなやつ」だ。

善は急げです。こういう画像を生成するシステムをjupyter notebookで作ってみます。


白黒画像を作る

まず画像を白黒にしなければなりません。
私たちが普段扱う画像データはRGB256諧調の3次元のデータとして保存されています。

赤、緑、青それぞれの階調値を$${red, green, blue}$$とすると、白黒画像の階調値は
$${gray = red \times 0.3 + green \times 0.59 + blue \times 0.11}$$
で求められます。赤、緑、青の階調値を全て$${gray}$$にすることで白黒画像になります。(参考: https://algorithm.joho.info/image-processing/rgb-to-gray-color-space/)

全ての画素でこの値を計算することで白黒画像にできます。

これをpythonで次のように実装しました。

def Convert2BWImg(img):
  outImg = np.zeros_like(img) # 一旦出力画像を0埋め
  for x in range(img.shape[0]): # すべてのxについて
    for y in range(img.shape[1]): # すべてのyについて
      b, g, r = img[x, y, :] # 青, 緑, 赤の画素値を取得
      gray = r * 0.3 + g * 0.59 + b * 0.11 # 白黒画素値を計算
      outImg[x, y, 0], outImg[x, y, 1], outImg[x, y, 2] = gray, gray, gray # 出力画像の青, 緑, 赤の画素値を全て計算した画素値に
  
  return outImg

この関数を呼び出して白黒画像を一旦作ってみます。

# 画像を白黒に変換
img = cv2.imread("drive/MyDrive/images/rikoriko.jpeg")
bw_img = Convert2BWImg(img)
cv2_imshow(bw_img)

元のカラー画像(左)がこのような白黒画像(右)にできました。


特定の色をもどす

元の画像の特定の画素値(RGBの値)のみを上で作った白黒画像につけてやれば欲しい映え画像が作れるはずです。
「この色だけ残す」という色を画素値で指定し、その画素値と同じ画素値のピクセルは色を戻す、というアルゴリズムで実装してみます。

def makeBaeImg(img, r, g, b):

  outImg = Convert2BWImg(img) # 一旦出力画像を白黒画像に
  
  for i in range(img.shape[0]): # 全てのi(x座標)について
    for j in range(img.shape[1]): # 全てのj(y座標)について
      pix = img[i, j] # pixは元画像の(i, j)
      if pix[0] == b and pix[1] == g and pix[2] == r: # もしb, g, r画素値が全て同じなら
        outImg[i, j, :] = img[i, j, :] # 出力画像の(i, j)を元の画像の色に戻す
  
  return outImg


実験

では映え画像を作っておしゃれインスタグラマーになりたいと思います。
残す画素値を調べるのにはmatplotlibというライブラリを用いたプログラムを作って使いました。(コードは記事の最後に添付しています。)

bae_img = makeBaeImg(img, 181, 66, 81)
cv2_imshow(bae_img)

上で作った関数を呼び出してできた画像を表示してみます。
このような画像ができました。

ん?できてなくない?

いや、よくみるとちょっっっとだけ色がついてるぞ。(胸部のアップになってしまいましたが他意はありません、ほんとに。)


何が問題だったのだろう

勘の良い方はお気づきかもしれませんが上で考えたアルゴリズムには問題があります。「全く同じ画素値」の画素のみを彩色する方法では欲しいような画像はできません。

服の赤色のみ戻そうと思ったのですが(また胸部のアップになってしまいましたが本当に他意はありません)、同じ色に見えても画像中の色はグラデーションになっていて全く同じ色ではありません。
だから彩色する色にはある程度幅を持たせなければなりません。

ということで実装を「指定の画素値との距離が閾値以下の画素を彩色する」というアルゴリズムに変更します。
「画素値の距離」はピンときにくいかもしれませんが、R, G, Bをそれぞれ直行する軸とすればただの三次元距離です。

閾値は繰り返し生成しながら上手く設定します。
実装はこのようにしました。

def dist(pix, ref_pix):
  return np.linalg.norm(pix - ref_pix)

def makeBaeImg2(img, r, g, b):
  th_dist = 100 # 彩色する距離の上限(画像ごとにうまく調整)

  ref_pix = [b, g, r] # 参照する画素値をリストに

  outImg = Convert2BWImg(img) # 一旦出力画像を白黒画像に
  
  for i in range(img.shape[0]): # 全てのi(x座標)について
    for j in range(img.shape[1]): # 全てのj(y座標)について
      pix = img[i, j] # pixは元画像の(i, j)
      if dist(pix, ref_pix) <= th_dist: # もし画素値の距離がth_dist以下なら
        outImg[i, j, :] = img[i, j, :] # 出力画像の(i, j)を元の画像の色に戻す
  
  return outImg


再実験

では画像を再生成してみます。今度こそオシャレインスタグラマーになることはできるのでしょうか!?

bae_img = makeBaeImg2(img, 181, 66, 81)
cv2_imshow(bae_img)

このような画像ができました。

いい感じなのではないでしょうか!?

まとめ

インスタでこういう画像を見かけて「どうやったら作れるんだろ?」と思い今回は作ってみました。(実際の画像編集ソフトでこのような実装をしているかは分かりません)
google colabのリンクも共有するので興味がある方はよかったら動かしてみてください。それでは。

ソースコード

画素値調べる用のコード

https://drive.google.com/file/d/12ARAozMbbF110xOP91pfx56C7gVxP41o/view?usp=sharing

google colabのリンク

この記事が気に入ったらサポートをしてみませんか?