乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

python

今回は画像に隠しメッセージを仕込める技術
ステガノグラフィーのご紹介です。


解説動画はこちら



ステガノグラフィーとは


ステガノグラフィー(Steganography)
画像、音声、動画などのデジタルデータに
別の情報を隠す技術のことです

これにより、情報を目に見えない形で
秘匿することができます。

早速情報を仕込んだ画像を作成してみましょう



ステガノグラフィーの仕組み

画像への埋め込み

最下位ビット(LSB)を利用して
画像の色をわずかに変更しメッセージを隠します。

最下位ビット(Least Significant Bit):
二進数の右端に位置する値

バイナリ変換:
メッセージをバイナリ形式に変換し終了ビットを追加

ピクセルの変更:
各ピクセルのRGB成分の最下位ビットを変更して
メッセージを埋め込み



ビット演算

変更対象のRGB値の各成分は0から255の範囲の整数
~1 は、ビット反転演算子

1 のビットを反転すると、全てのビットが反転し
...11111110 というビット列になる

RGB値 が 5の場合(バイナリで 00000101)
~1 は ...11111110 となり
5 & ~1 は 00000101 & 11111110 となり
結果は 00000100(つまり 4)になる

00000101 &
11111110



00000100


ピクセルの変更

各ピクセルのRGB成分から最下位ビットを取得し
バイナリメッセージの値に変更します


メッセージの復元

バイナリメッセージを8ビットごとに分割し、文字に変換
終了ビット(11111111)が見つかるまでメッセージを復元する



音声ファイルや動画ファイルにも同様の手法が使われ
音声の波形や動画のフレームに情報を埋め込むことが可能です。


埋め込む画像


元画像はこんな感じです。
sample





画像にメッセージを埋め込むコードサンプル

from PIL import Image

def encode_image(image_path, secret_message, output_path):
    # 画像を開く
    image = Image.open(image_path)
    encoded_image = image.copy()

    # メッセージをバイナリに変換
    binary_message = ''.join(format(ord(char), '08b') for char in secret_message) + '11111111'  # 終了ビット
    data_index = 0

    # 画像のピクセルを走査
    for y in range(encoded_image.height):
        for x in range(encoded_image.width):
            pixel = list(encoded_image.getpixel((x, y)))
            for i in range(3):  # RGBの各成分
                if data_index < len(binary_message):
                    pixel[i] = (pixel[i] & ~1) | int(binary_message[data_index])  # 最下位ビットを変更
                    data_index += 1
            encoded_image.putpixel((x, y), tuple(pixel))
            if data_index >= len(binary_message):
                break
        if data_index >= len(binary_message):
            break

    encoded_image.save(output_path)

# 使用例
input_img_path = "画像のパス"
input_message = "埋め込むメッセージ"
output_img_path = "出力後のパス"
encode_image(input_img_path, input_message, output_img_path)

実行するとメッセージを埋め込んだ画像が生成されます。

元の画像と比較すると

download


差分はほとんどわからないですね!!


画像からメッセージを取り出すコードサンプル

from PIL import Image

# バイナリメッセージを8ビットごとに分割して文字に変換
def make_message(binary_message):
    message = ''
    for i in range(0, len(binary_message), 8):
        byte = binary_message[i:i+8]
        if byte == '11111111':  # 終了ビットを検出
            break
        message += chr(int(byte, 2))
    return message

def decode_image(image_path):
    image = Image.open(image_path)
    binary_message = ''

    # 画像のピクセルを走査
    for y in range(image.height):
        for x in range(image.width):
            pixel = image.getpixel((x, y))
            for i in range(3):  # RGBの各成分
                binary_message += str(pixel[i] & 1)  # 最下位ビットを取得

    return make_message(binary_message)

# 使用例
decoded_message = decode_image('output_image.png')
print("Decoded message:", decoded_message)
Decoded message: Hello Otupy

メッセージが復元されました。




最後に

こちらの画像にはメッセージを埋め込んでいるので
ダウンロードして解読してみてくださいね

bbbb




今回はステガノグラフィーをご紹介しました
画像などに隠しメッセージを残せるので

暗号のように秘匿したいやりとりで
使うこともできたりして
なかなか面白い技術です。

いろいろ遊んでみてみてくださいね
それでは



今回は重力レンズエフェクトで
画像を歪ませで遊んでみました。


解説動画はこちら


 

重力レンズとは

光が天体の重力によって曲げられ
天体があたかもレンズとして働く効果のことです

光の曲がり具合はレンズとなる天体の質量が大きいほど強く
背後の銀河から来る光が強く曲げられて像が大きく歪んで見えます


今回はレンズをブラックホールで想定して
擬似的な効果の計算で画像を作成するコードのご紹介です。


基本の計算式

レンズ中心からの距離が小さくなるほど
(レンズに近づくほど)歪みが強くなるような計算です

元々の重力レンズの計算は少し難解かなと思いましたので
簡易な計算でエフェクトをかけます。

スクリーンショット 2024-11-23 17.08.32




処理の流れ

入力画像をNumPy配列に変換し、全ピクセルの座標を計算
重力レンズ効果を適用する領域を特定

レンズ中心からの距離に基づいて歪みを計算し、新しいピクセル座標を算出
新しい座標に基づいてピクセルを再配置し、ブラックホール領域を黒で塗りつぶし
最終的な画像として出力


ipywidgets でUIを作って操作できます。
スライダーの値を変えると、画像が変化します。

Google colabなどで試す事ができます。


コードはこちら
from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, IntSlider, FloatSlider

cached_image = None

# 重力レンズエフェクトをかけた画像を生成する
def apply_gravitational_lens(image, lens_radius, black_hole_radius, distortion_factor):
    
    # 画像をNumPy配列に変換
    input_pixels = np.array(image)
    width, height = image.size
    cx, cy = width // 2, height // 2
    
    # 座標グリッドの生成と中心からの相対座標,距離の計算
    y, x = np.meshgrid(np.arange(height), np.arange(width), indexing="ij")
    dx, dy = x - cx, y - cy
    sx, sy = x.copy(),y.copy()
    distance = np.sqrt(dx**2 + dy**2)

    # 歪みを適用 { distortion = 1+distortion_factor * ((lens_radius - distance[lens_mask]) / lens_radius)**2 }
    lens_mask = (black_hole_radius < distance) & (distance < lens_radius)
    distortion = 1 + distortion_factor * ((lens_radius - distance[lens_mask]) / lens_radius)**2
    sx[lens_mask] = cx + dx[lens_mask] * distortion
    sy[lens_mask] = cy + dy[lens_mask] * distortion

    # 座標のクリッピングと出力画像の生成
    sx = np.clip(sx, 0, width - 1).astype(int)
    sy = np.clip(sy, 0, height - 1).astype(int)
    output_pixels = input_pixels[sy, sx]

    # ブラックホール領域の適用
    black_hole_mask = distance <= black_hole_radius
    output_pixels[black_hole_mask] = [0, 0, 0]
    return Image.fromarray(output_pixels)

# インタラクティブUI部
def interactive_gravitational_lens(image_path):

    global cached_image
    if cached_image is None:
        cached_image = Image.open(image_path).convert("RGB") 

    def update(lens_radius, black_hole_radius, distortion_factor):
        output_image = apply_gravitational_lens(
            cached_image,
            lens_radius=lens_radius,
            black_hole_radius=black_hole_radius,
            distortion_factor=distortion_factor,
        )
        plt.figure(figsize=(8, 8))
        plt.imshow(output_image)
        plt.axis('off')
        plt.title(f"Lens Radius: {lens_radius:03}, Black Hole Radius: {black_hole_radius:03}, Distortion: {distortion_factor:.02}")
        plt.show()

    interact(
        update,
        lens_radius=IntSlider(min=50, max=990, step=10, value=50, description="Lens Radius"),
        black_hole_radius=IntSlider(min=10, max=200, step=5, value=10, description="Black Hole Radius"),
        distortion_factor=FloatSlider(min=0.1, max=10.0, step=0.1, value=0.1, description="Distortion Factor"),
    )

interactive_gravitational_lens("画像パス")

download-1

レンズの半径と
歪み具合を調整して
面白画像を作って遊んでみてください。

動画では色々な画像で試しているので
ぜひみてみてくださいね

今回はこれまでです
それでは。



今回はまじめに
最近のPythonの文法をまとめてみました。

解説動画はこちら


 
さて最近のPythonの文法について
しばらく触れていなかったので
少し解説していきたいと思います。

今回はGoogle Colabを使用しているので
そのバージョンである3.10までの内容です。




文字列のフォーマット指定

f"文字列{変数}"
で文字列にデータを差し込みすることができます。
# 昔のフォーマット
name = "フリーザ"
hourly = 530000
text = "{0} : 私の時給は{1}円です。".format(name, hourly)
print(text)

# Fフォーマット
name = "フリーザ"
hourly = 530000
text = f"{name} : 私の時給は{hourly}円です。"
print(text)
フリーザ : 私の時給は530000円です。

フォーマットの指定もできます

{変数:指定文字列}
{変数:,} : 3桁区切り
{変数:.2f} : 小数点以下の指定(この場合は2桁)

# Fフォーマット
name = "フリーザ"
name2 = "ザーボン"
hourly = 530000
hourly2 = 2980.2983
text = f"{name} : 私の時給は{hourly:,}円です。"
print(text)
text2 = f"{name2} : 私の時給は{hourly2:.3f}円です。"
print(text2)


defaultdict

存在しないキーのデフォルト値を指定できます

通常の辞書型では最初にキーが存在しないと値を操作できません
# キーを初期化代入する必要がある
d = {}
for key in "aabbbc":
    if key not in d:
        d[key] = 0
    d[key] += 1

print(d)

defaultdictの場合は初期値を決められるので
コードを省略できます。

# defaultdictの場合
from collections import defaultdict
d = defaultdict(int)

for key in "aabbbc":
    d[key] += 1

#print(d)
print(d["a"])
2



関数の結果をキャッシュ

functools.lru_cacheを用いると
同じ引数の関数の結果をキャッシュでき
再帰の計算などが早く行えるようになります。

# キャッシュ無し
def fibonacci_old(n):
    if n < 2:
        return n
    return fibonacci_old(n-1) + fibonacci_old(n-2)
print(fibonacci_old(30))
msレベル

# キャッシュあり
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_cache(n):
    if n < 2:
        return n
    return fibonacci_cache(n-1) + fibonacci_cache(n-2)
print(fibonacci_cache(100))
usレベル

圧倒的に早くなります。


アンパック演算子

*を用いて変数への複数代入を行う

a, *b, c = [1,2,3,4,5,6]
print(a, b, c)

a, b, *c = [1,2,3,4,5,6]
print(a, b, c)
1 , [2,3,4,5] , 6
1 , 2 , [3,4,5,6]



ウォルラス演算子

:= で変数への代入と評価を同時にできるようになります

(a:=2) >= 2
True


WITH構文で複数ファイルのコンテキスト

with open(ファイル名) as 変数, open(ファイル名) as 変数 ... :

# ファイルを作っておく
import random
import string

for name in ["a","b","c"]:
  with open(name+".txt","w") as _w:
    text = ''.join([random.choice(string.ascii_lowercase) for i in range(10)])
    _w.write(text)

# with構文
with open("a.txt") as _a, open("b.txt") as _b, open("c.txt") as _c:
  print(_a.read())
  print(_b.read())
  print(_c.read())


match文

3.10でmatch文が導入された
他の言語でいうcase文ですね

match 条件:
  case パターン:
    return 戻り値
  case パターン:
    return 戻り値


# match文
def http_error(status):
    match status:
        case 400:
            return "ダメっすねー"
        case 404:
            return "見つからねっす"
        case 418:
            return "なんでしょね"
        case _:
            return "何言ってるか分かんねっす"

http_error(404)
見つからねっす


いつの間にか便利な機能が追加されていて
業務でも捗っている感じになっていました。

文法の知識は定期的にアップデートしていかないと
もったいないですね

今回は最近のPython文法についてでした
それでは。


今回は先日公開されたジブリ映画に関する
ジブリ公式ツイッターさんの投稿に関するものです

解説動画はこちら


ジブリ公式Twitterの投稿が
まるでモールス信号のようだと
話題になっていたので解読してみました

対象のツイートはこちら

ジブリ公式ツイッター


宮崎駿監督の最新作「君たちはどう生きるか」の
ポスターに描かれた謎の鳥のイラストとともに
「カカヘカ・・・」と暗号文ぽいものが
書かれています

鳥の鳴き声のようにも見えるますが・・・
モールス信号に変換する事で
解読する事ができる様です

 

書かれている文字

ツイートされていたのはこの文章
カカヘカヘッカヘヘッヘカヘカヘッカカッヘカヘカカッヘカヘカヘッカヘカヘカッヘカカヘッカヘッヘカヘカッカカヘカッヘカヘカカッカヘッカヘカヘヘッヘカヘヘカッヘヘッ

「カ」は「・」
「ヘ」は「-」
「ッ」は区切り
として和文モールス符号に
変換することが出来るみたいです

早速解読するコードを作っていきましょう

モールス暗号解読コード
md = {
'カヘ': 'イ','カヘカヘ': 'ロ','ヘカカカ': 'ハ','ヘカヘカ': 'ニ',
'ヘカカ': 'ホ','カ': 'ヘ','カカヘカカ': 'ト','カカヘカ': 'チ',
'ヘヘカ': 'リ','カカカカ': 'ヌ','ヘカヘヘカ': 'ル','カヘヘヘ': 'ヲ',
'ヘカヘ': 'ワ','カヘカカ': 'カ','ヘヘ': 'ヨ','ヘカ': 'タ',
'ヘヘヘ': 'レ','ヘヘヘカ': 'ソ','カヘヘカ': 'ツ','ヘヘカヘ': 'ネ',
'カヘカ': 'ナ','カカカ': 'ラ','ヘ': 'ム','カカヘ': 'ウ','カカヘヘ': 'ノ',
'カヘカカカ': 'オ','カカカヘ': 'ク','カヘヘ': 'ヤ','ヘカカヘ': 'マ',
'ヘカヘヘ': 'ケ','ヘヘカカ': 'フ','ヘヘヘヘ': 'コ','ヘカヘヘヘ': 'エ',
'カヘカヘヘ': 'テ','ヘヘカヘヘ': 'ア','ヘカヘカヘ': 'サ','ヘカヘカカ': 'キ',
'ヘカカヘヘ': 'ユ','ヘカカカヘ': 'メ','カカヘカヘ': 'ミ','ヘヘカヘカ': 'シ',
'カヘヘカカ': 'ヱ','ヘヘカカヘ': 'ヒ','ヘカカヘカ': 'モ','カヘヘヘカ': 'セ',
'ヘヘヘカヘ': 'ス','カヘカヘカ': 'ン','カカ': '゛','カカヘヘカ': '゜'
}

def morus_decode(text):
    texts = text.replace("\n","").split("ッ")
    result_text = ''.join([md[tx] for tx in texts if tx in md])
    print(result_text)

辞書を使って対象の文字列を
変換してつなげるだけのシンプルで簡単なコードです

これを使って先ほどの文章を入力すると
text  = """
カカヘカヘッカヘヘッ
ヘカヘカヘッカカッヘカヘカカッ
ヘカヘカヘッカヘカヘカッヘカカヘッカヘッ
ヘカヘカッカカヘカッヘカヘカカッカヘッ
カヘカヘヘッヘカヘヘカッヘヘッ
"""

morus_decode(text)

答えが出ます!!!!!!!

答えを知りたい方は
動画を見るかコード試してみて下さいね




平文をモールス暗号に変換するコード
md2 = {v:k for k,v in md.items()}
def morus_code(text):
    result_text = 'ッ'.join([md2[tx] for tx in text if tx in md2])
    print(result_text)
    
text = "ココニテキストヲ"
morus_code(text)

逆でテキストをモールス暗号化するのも
辞書変換で出来ます

Python言語を使えば
こういった暗号解読も簡単に行えます

文法がまだの方は
是非この機会に
色々動画見てみて下さいね

それでは

乙py式5時間で学ぶプログラミング基礎(python編)


乙py3

短時間でプログラミングを学ぶ事ができる動画講座を作りました。

YOUTUBEの動画と資料を載せておきますので
ぜひ参考にしてくださいませ。


なおPythonチートシートを作成しています。


コーディングに迷った際に役に立ち

WEB検索する時間を無くして

作業時間を効率化できます。

note
Pythonチートシート

目次




1.この講座について
2.プログラミング言語について
3.プログラミング言語の学習方法について
4.プログラミングの学習環境について
5.目標設定について
6.Jupyterの使い方
7.演算
8.文字
9.インデックス
10.変数
11.予約語
12.データ型
13.文字列
14.文字列のフォーマット
15.算術演算子
16.代入演算子
17.関係演算子
18.論理演算子
19.基礎演習1
20.リスト1
20.リスト2
21.タプル
22.辞書型
23.if文
24.for文
25.while文
26.組み込み関数について
27.enumerate関数
28.zip関数
29.ソート
30.内包表記
31.基礎演習2
32.関数
33.global変数とスコープ
34.無名関数
35.オブジェクト指向の話
36.クラスについて
37.例外処理
38.ライブラリの利用
39.基礎演習3
40.まとめ
付録.その先、仕事とか、研究にどう使えるの?

このページのトップヘ