シグモイド関数でコントラスト強調
たくさんの画像のコントラストを上げようとしたのですが、どんなアプリを使えばいいのかわからず、そもそもコントラストを上げると言っても、単純に値の上下数%を飽和させる方法とか、イコライザを使う方法とかいくつもあるようなので、自分の勉強も兼ねて適当に実装してみることにしました。今回はとりあえず目的に合っていそうだった、シグモイド関数を使ったコントラスト強調を試してみます。
ガンマ補正
いきなり本題とは少しずれた話題ですが、ほとんどのカラーの画像(sRGBの画像)ファイルにはガンマ補正がかかった値が格納されています(sRGBではない、例えばHDRの画像やグレースケールの画像はガンマ補正がないっぽいです)。PhotoshopやGIMPだとそのあたり自動で処理してくれているはずなのですが、自分で実装する場合は考慮した方がいいと思います。コントラストの強調だけなら無視してもよさそうですが…
ガンマ補正というのは、昔のCRTディスプレイが入力に対して出力の特性が直線ではなく、
に近い特性になっていて、これを補正するための処理のことです。具体的には
となるような処理が画像ファイルのデータにはかけられています。
今回処理しようとした画像はsRGBで、上記の補正がかかっているので、画像処理をかける前にガンマ補正を除去する処理を行います。
シグモイド関数
シグモイド関数というのは下記の関数で、S字に似た形をしています。結構いろんなところで活躍しているひとらしいです。
この形を利用して、明るいところはより明るく、暗いところはより暗くしようというのが今回の目的です。
コントラスト強調
素のままのシグモイド関数は、変曲点がになっています。コントラスト強調に使用するため、軸をだけずらして
とします。は、コントラストを強調したい場所になります。例えば、値が20%の周囲でコントラストを強調したいなら、とします。は、どのくらいコントラストを強調するかですが、決定には試行錯誤が必要になると思います。
また、このままだとの範囲がからになっていないので、スケーリングを行います。最終的に、今回のコントラスト強調は下記のようになります。
上記のグラフはの場合ですが、値が0.5より低いところではより暗く、0.5より高いところではより明るく、またのときは、のときはになっているのがわかります。
Pythonで実装してみる
import math import numpy as np from PIL import Image import sys import os def sigmoid(x): a = 5.0 b = 0.5 return 1.0 / (1.0 + math.exp(a * (b - x))) lut = np.empty(256) sigmoid0 = sigmoid(0.0) sigmoid1 = sigmoid(1.0) for i in range(256): x = i / 255.0 x = x ** 2.2 # ガンマ補正を外す x = (sigmoid(x) - sigmoid0) / (sigmoid1 - sigmoid0) # コントラスト補正をかける x = x ** (1.0/2.2) # 再びガンマ補正をかける lut[i] = 255.0 * x for fn in sys.argv[1:]: img = np.asarray(Image.open(fn)) img = lut[img] ofn = os.path.splitext(fn)[0] + "-out.png" Image.fromarray(np.uint8(img)).save(ofn, quality=95) print("%s done." % fn)
numpy便利!
FF14のSSで試してみる
元の画像。
とした場合。
のときは、全体が明るくなります。 とした場合。
【FF14】【ID愛でる会Aegis】ラバナスタ観光
愛でる会で不定期にやっている24人レイドの観光、今回はラバナスタに行ってきました。最近では毎週行っているところですが、ゆっくり観光していくと普段とは違うところも見ていくことができます。
パーティ編成
今回はパーティ募集がアライアンスに対応したので、ぶっちゃけてしまうと集合する必要はなかったのですが、今回も今までと同じように集合場所を決めてそこでパーティ編成をしました。今回は、クガネランディングの近くの船の上に集合。出発まで思い思いのことをやっている参加メンバーたち。
みんなログインしたらプライベート募集でパーティを編成して、突入!と思ったら、コンテンツファインダーサーバーのエラーが云々というメッセージ…! どうもアライアンスのリーダーしか申請できないようなのですが、あのエラーメッセージではそのことに気付かずしばらく混乱していました。みんなでよしだああ!!って叫んだあと無事に突入。
ラバナスタ地上部
1ボスは普通に撃破。自分が参加したCアラではメガネがドロップ。今回は不参加だったけど、愛でる会にはメガネ好きで有名なたけおさんがいらっしゃって、ラバナスタ実装初期にはメガネ入手のために随分苦労されていたのですが…
2ボスはかなり苦戦。砂球が爆発して2回全滅しました。初見の方もいるから仕方ない。撃破したあとの雑魚戦があるエリアには、周囲に赤チョコボの卵っぽいものがあります。オムレツ食べたい…
帝国の魔導兵器の残骸。最近戦闘があったとNPCが言っていますが、まだ燃えています。青い炎なのは青燐機関の燃料が燃えてるから、かな。
水路
今回はここで一旦7分くらいの自由時間。
空中浮遊ララフェル。実際には水に浮いているだけなのですが、タレットを水面に置くと水上と水中が両方見えるのでこんなことに。紅玉海では青くなっちゃうのですが、ここの水は透明です。
NPCも話していますが、潜水する場所はかなり水深が深いです。
このエリア、天井を見上げても綺麗です。
地下
3ボス、4ボスは特に問題なく撃破。3ボスさん、やっぱりグラフィックの作り込みがすごい。今回は3ボス撃破後に2回目の自由行動。
4ボスの戦闘前、あの仮面がボスの近くに浮いてます。
4ボスの途中で出てくるあの人、スカラタンク装備かな?
ダークアルテマ後は仮面が増えます。
啓示の時はしっかり仮面を装着している4ボスさん。
#FF14 #ID愛でる会Aegis ラバナスタ4ボスの仮面装着
— けさらんぱ@Aegis (@KesaranPa) 2018年1月21日
仮面がボスの周囲をくるくる回って、着弾の瞬間に装着。 pic.twitter.com/k8u2j0LSzD
そうこうして無事にクリアしてみんなで集合写真を撮影して終了です。主催してくれたすぱさん、はなおん、本当にありがとうです!
Pythonで大量のExcelファイルをいじった話
最近「100個のテキストファイルから一部を取り出して100個のExcelファイルを作る」とか、「100個のExcelファイルの一部のセルを1つのExcelファイルにコピペする」とかいう感じの作業をする必要があって、手作業では面倒すぎるのでPythonのopenpyxlを使ってみました。今回はその時のメモです。
openpyxlは、Pythonからxlsxファイルを読み書きするためのパッケージです(xlsファイルは読み書きできないはず)。Excelを操作するわけではないので注意しないといけない点があるものの、逆にExcelなしでExcelファイルをいじることができます。
準備
Pythonとopenpyxlのインストール
Linuxだと標準でPythonが入っていることが多いのですが、今回はWindows環境で作業する必要がありました。WindowsでPythonを動かす方法はいくつかあるみたいですが、自分はAnacondaをインストールしました。この場合、普通にインストールするとopenpyxlも一緒にインストールされます。
基本的な使い方
ファイルの読み込みと書き込み
インポートは次のようにしています。
import openpyxl as px
ファイルの読み込みは
wb = px.load_workbook("src.xlsx")
書き込みは
wb.save("dst.xlsx")
みたいな感じで行います。新規ファイルを作成する場合は
wb = px.Workbook()
らしいです。
シートの操作
シートを作成するときは
ws = wb.create_sheet(title="シート名")
とします。既存のシートを名前で選択して操作するときは
ws = wb["シート名"]
現在アクティブなシートを操作するときは
ws = wb.active
とします。シートの名前は
ws.title = "シート名"
で変更できます。
セルの読み込みと書き込み
セルの中身の読み込みは
data = ws['A1'].value
書き込みは
ws['A2'] = data
でできます。数式も書けます。
ws['A3'] = "=1+2"
簡単!
または
data = ws.cell(row=1, column=1).value ws.cell(row=2, column=1).value = data
でも読み書きできます。ループではこちらの方が便利かも。
その他
他にもフォントや罫線を変えたり、セルを結合したりできるみたいなのですが、自分の用途ではExcelであらかじめ見た目だけを整えたxlsxファイルを作って、 shutil.copy()
でコピーしてから値を変える方が楽だったので試していません。
はまったこと
罫線が壊れる!
結合されたセルに罫線があるExcelファイルを読み込んだ場合、openpyxlでいじってから保存すると罫線が壊れます。既知の問題らしく、海外でMonkeyPatchが紹介されていました。下記はそのままのコピペです。この記述をソースのどこかに書いておきます。 追記:v2.6で修正されるようです。
from itertools import product import types import openpyxl from openpyxl import worksheet from openpyxl.utils import range_boundaries def patch_worksheet(): """This monkeypatches Worksheet.merge_cells to remove cell deletion bug https://bitbucket.org/openpyxl/openpyxl/issues/365/styling-merged-cells-isnt-working Thank you to Sergey Pikhovkin for the fix """ def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None): """ Set merge on a cell range. Range is a cell range (e.g. A1:E1) This is monkeypatched to remove cell deletion bug https://bitbucket.org/openpyxl/openpyxl/issues/365/styling-merged-cells-isnt-working """ if not range_string and not all((start_row, start_column, end_row, end_column)): msg = "You have to provide a value either for 'coordinate' or for\ 'start_row', 'start_column', 'end_row' *and* 'end_column'" raise ValueError(msg) elif not range_string: range_string = '%s%s:%s%s' % (get_column_letter(start_column), start_row, get_column_letter(end_column), end_row) elif ":" not in range_string: if COORD_RE.match(range_string): return # Single cell, do nothing raise ValueError("Range must be a cell range (e.g. A1:E1)") else: range_string = range_string.replace('$', '') if range_string not in self._merged_cells: self._merged_cells.append(range_string) # The following is removed by this monkeypatch: # min_col, min_row, max_col, max_row = range_boundaries(range_string) # rows = range(min_row, max_row+1) # cols = range(min_col, max_col+1) # cells = product(rows, cols) # all but the top-left cell are removed #for c in islice(cells, 1, None): #if c in self._cells: #del self._cells[c] # Apply monkey patch worksheet.Worksheet.merge_cells = merge_cells patch_worksheet()
値を読みたいのに数式が読まれる!
openpyxlではvalueという属性でデータにアクセスできますが、これでデータを読み出すとExcelでいうところの「値」ではなく「数式」が読み出されます。値を読み出す場合は、ワークブックを開くときに data_only
オプションを指定します。
wb = px.load_workbook(filename, data_only=True)
ちなみに、Excelでxlsxファイルを保存すると、数式のセルは数式と値の両方がファイルに書き込まれますが、openpyxlでは数式しか書き込まれません。もしopenpyxlで数式を書き込んだファイルからそのまま値を読み出すとNoneが読み出されます。そのため、openpyxlで数式を書いたファイルから値を読み出すには、一度Excelでファイルを開いて保存する必要があります。