ETL文字データベースを画像に変換

f:id:hanamichi_sukusuku:20210211213910p:plain

実行結果

実行するとカタカナの画像データが複数のディレクトリに保存されている。

 

保存先のディレクトリの作成

outdir = "png-etl1/"
if not os.path.exists(outdir): os.mkdir(outdir)

 

ETLデータの中身のファイル名を全て取得

files = glob.glob("ETL1/*")

 

ファイルごとに処理をしていく

for fname in files:
  if fname == "ETL1/ETL1INFO": continue # 情報ファイルは飛ばす
  print(fname)
  # ETL1のデータファイルを開く --- (*2)
  f = open(fname, 'rb')
  f.seek(0)
  while True:
  # メタデータ+画像データの組を一つずつ読む --- (*3)
    s = f.read(2052)
    if not s: break
  # バイナリデータなのでPythonが理解できるように抽出 --- (*4)
    r = struct.unpack('>H2sH6BI4H4B4x2016s4x', s)
    code_ascii = r[1]
    code_jis = r[3]
  # 画像データとして取り出す --- (*5)
    iF = Image.frombytes('F', (64, 63), r[18], 'bit', 4)
    iP = iF.convert('L')
  # 画像を鮮明にして保存
    dir = outdir + "/" + str(code_jis)
    if not os.path.exists(dir): os.mkdir(dir)
    fn = "{0:02x}-{1:02x}{2:04x}.png".format(code_jis, r[0], r[2])
    fullpath = dir + "/" + fn
  #if os.path.exists(fullpath): continue
    enhancer = ImageEnhance.Brightness(iP)
    iE = enhancer.enhance(16)
    iE.save(fullpath, 'PNG')

f=open(fname, 'rb')でファイルを開く。

f.seek(0)では読み込む位置を指定している。

例えば

(text.txt)

text

note

book

pen

 

上記のようなtext.txtというテキストファイルがあった時に必ず先頭から読み込まれるとは限らない。そこで現在の位置を知るためには

with open('text.txt', 'r') as t:

    print(t.tell())

    print(t.read(4))

>>0

>>text

tell()で現在の位置を知ることができる。この場合でいうとtextのtの位置にいることがわかる。そして、read()で引数に4を指定しているが現在の位置から4番目のものを読み込むことでtextと表示されている。

seek()ではこの位置を変更することができる。

例えば

with open('text.txt', 'r') as t:

    t.seek(5)

    print(t.tell())

    print(t.read(4))

>> 5

 

>>note

改行文字が入るので5番目がnoteのnの部分になる。seek(5)とすることで5番目の部分に位置を移動することができた。

f.seek(0)では先頭から読み込むように位置を指定している。

 

s = f.read(2052)この部分では今回扱うデータは2052バイトの固定長になっているため先頭から2052番目までを読み込んでいる。
この直後にprint(f.tell())を記述すると

f:id:hanamichi_sukusuku:20210211213706p:plain

それぞれ位置が移動し読み込んでいるのがわかる。

 

バイナリーデータなのでpythonが理解できるように抽出

r = struct.unpack('>H2sH6BI4H4B4x2016s4x', s)

structモジュールはバイナリーデータを処理するときのもの。structモジュールを使うことで細かくフォーマットを指定してバイナリーデータを作成したり、バイナリデータから数値に変換したりすることに利用することができる。

主な使い方はパックとアンパックがあり、パックは数値型などの値をフォーマットを指定してbytes型に変換すること、

pack(フォーマット, 値)

アンパックはbytes型のバイナリーデータを元の型の値に戻すこと。

unpack(フォーマット, バイナリーデータ)

今回はバイナリーデータをpython理解できるようにしたいのでアンパックを使用する。

>はビックエンディアンと言ってバイトがバイナリデータが最上位ビットから並べられている時に使用。例えば、\x00\x01では、\x00が上位バイトで\x01が下位バイトとなる。

unpackのフォーマットで指定するバイト数をバイナリーデータのバイト数が同じ出ないとエラーになる。

2sとか6Bとか指定するフォーマット文字の前に数値を与えるとその値✖️フォーマット文字になる。2sならss、6BならBBBBBB。2016sは2016回sを指定することになる。今回の場合合計すると2052バイトになる。

それぞれのフォーマット文字列が何を示すのかはドキュメントなどを見ればわかったがread()した中身をみてもなぜそのフォーマット文字をしてするのかわからなかった。

とりあえずこの処理でpythonが理解できるような意味のある単位ごとに抽出する。

 

画像データとして取り出す

iF = Image.frombytes('F', (64, 63), r[18], 'bit', 4)

画像処理ライブラリであるpillowから画像を処理するimageモジュールのfrombytes()関数を利用してバイト列から画像を生成していく。

PIL.Image.frombytes(mode, size, data, decoder_name='raw', *args)

mode(第一引数)には生成する画像のモードを指定する。データのモードとこのモードは合わせる必要がある。

size(第二引数)には生成する画像のサイズを指定する。(width, height)のフォーマットをタプルで渡す。

data(第三引数)にはモードに指定されたバイト列(データ)を渡す。

decoder_name='raw(第四引数)ここにはデコーダーの名前を指定。デフォルトはraw。

args(第五引数)ここにはデコーダーの引数を指定。

返り値はImageオブジェクト。

 

浮動少数点の構造、符号、仮数、指数で表せれる。

f:id:hanamichi_sukusuku:20210212173322p:plain

同じようにこの「符号」、「仮数」、「指数」ビットで表現します。浮動小数点では 32 ビットを以下のように 3 つに分割。

f:id:hanamichi_sukusuku:20210212174545p:plain

32ビットの浮動小数を表すのはこの形。

今回の第三引数に指定したデータは32ビットの浮動小数で表されているため第一引数にはそのモードを指す"F"が入る。

 

第三引数のr[18]はstruct.unpack()で格納した変数rの中身を見るとインデックスで18番目から画像を表すデータになっている。

 

iP = iF.convert('L')

ここではImageオブジェクトが格納されたiFにconvert("L")を指定することでグレースケールに変換している。

 

fn = "{0:02x}-{1:02x}{2:04x}.png".format(code_jis, r[0], r[2])

ここでは画像ファイル名を作成しており、format()メソッドを使用する。

format()メソッドは引数に指定したものを文章に置き換えることができる。

"{0}さんは{1}cmです".format("山田", 180)

>>山田さんは180cmです

このように引数のインデックス番号に対応して値が格納される。

書式を指定したい場合は

{インデックス番号: 書式設定} この形で指定する。

今回だと:の左側がインデックス、右が書式設定なので02xや04xが書式設定。

0はゼロを先頭に追加するオプションで、xは16進数で表すことを指定している。xの前の2や4は表示する桁数を指定。文字列なら左詰め、数値なら右詰めで桁をピックアップする。

fullpath = dir + "/" + fn

 

ファイル名ができたのでパスを作成。

 

enhancer = ImageEnhance.Brightness(iP)

ImageEnhanceモジュールで画像の明るさを変える。返り値はenhanceオブジェクト。

iE = enhancer.enhance(16)

ImageEnhanceモジュールで作成したenhanceオブジェクトにenhance()メソッドを適用する。引数には1.0が元画像と同じ明度で大きくなるにつれて明るく、小さくなるにつれて暗くなる。

 

iE.save(fullpath, 'PNG')

save()メソッドで保存先のパスと拡張子を指定して保存。