思考ノイズ

無い知恵を絞りだす。無理はしない。

画像に集中線をいれるコード

久しぶりにPythonのコーディングネタ。軽めですが。

モチベーション

employment.en-japan.com

このネタ。でも正直テストとかよくしらないです。ただ集中線を描くプログラムというワードにピンときて書いてみたくなりました。最近はOpenCVを使ったコーディングにめぞめぞしていたので丁度よい題材でした。

まずはコードを

みてくださいー。

import cv2
import math
import random


def setEffect(pic, center = (0.5, 0.5)):
    he, wi = pic.shape[:2]
    leng = math.sqrt(he ** 2 + wi ** 2)

    for rd in range(360):
        k = rd * math.pi/180
        r = random.uniform(0.20,0.50)

        x = wi * center[0] + r * leng  * math.cos(k)
        y = he * center[1] + r * leng  * math.sin(k)
        dx = wi * center[0] + leng * math.cos(k)
        dy = he * center[1] + leng * math.sin(k)
            
        cv2.line(pic, (int(x), int(y)), (int(dx), int(dy)), (0, 0, 0), 2)
    cv2.imshow('test', pic)
    cv2.waitKey(0)
    cv2.destryAllWindows()
            
img = cv2.imread("test.jpg")
setEffect(img)

例として拾い物のPUBGというネットワークゲームのスクショをいれてみますね。ちょうどよいのこれしかなかった。 f:id:bython-chogo:20170912222746j:plain これを入れると、以下の画像が出てきます。 f:id:bython-chogo:20170912223039j:plain

うひょーい、できましたー!

解説

画像データを引数にとっております。あと、集中線の中心となる場所を指定できます。Defaultでど真ん中にしております。 あと、画像の縦横の長さを取得して起きます。そのデータから集中線の長さのもととなる値を計算しています。十分な長さを確保するため、画像の対角線の長さにしてあります。

def setEffect(pic, center = (0.5, 0.5)):
    he, wi = pic.shape[:2]

    leng = math.sqrt(he ** 2 + wi ** 2)

中心から360ぐるっと回る繰り返しで1度ずつ線を引くようにします。kは度数から三角関数の引数となる値の計算をします。2 pi が360度なので、1度は pi/180ですね。 あと、集中線の長さをランダムに指定します。r は0.2 ~ 0.5 までのFloatの乱数を出します。

    for rd in range(360):
        k = rd * math.pi/180
        r = random.uniform(0.20,0.50)

線引きの描写です。 各度数ごとに(x, y) から (dx, dy)の線をひくのですが、中心は先ほど指定したように画像のど真ん中です。先ほど求めた対角線の長さ leng を放射状に直線を引きますが、線の引き始めはランダム値の割合 (0.2-0.5)からになります。終わりはleng の終わりまで。対角線の長さにしたのはどの中心地からも必ず画像の外側に行くからです。。 角度はcos(k), sin(k)で指定します。

        x = wi * center[0] + r * leng  * math.cos(k)
        y = he * center[1] + r * leng  * math.sin(k)
        dx = wi * center[0] + leng * math.cos(k)
        dy = he * center[1] + leng * math.sin(k)
            
        cv2.line(pic, (int(x), int(y)), (int(dx), int(dy)), (0, 0, 0), 2)

imshowで表示。そのままだと画像が一瞬で消えてしまいますので、waitKey(0)で待たせておきます。 destryAllWindows()できちんとお掃除をしておきます。

    cv2.imshow('test', pic)
    cv2.waitKey(0)
    cv2.destryAllWindows()

感想

行数自体は長くないのですが、数学でならった円(楕円)や三角関数の公式を思い出すのにちょっとだけ時間がかかりました。 プログラムをよく書くようになって数学的センスって大事。今更、勉強をさぼった過去を悔いてもしょうがないのでこうやって書きながら思い出しながらいけたらよいかと思ってます。

打ち上げ花火、下から見るか?横から見るか?

今年の夏を終わらすために見に行きました。とりあえず見ないと夏が終わりそうになかった。

www.uchiagehanabi.jp

もう30も半ばも超えるあたりで、いまだに中二病感を引きずり続けながらもそろそろ卒業せんといけないです。夏の終わりとともにこの映画でおれの中二病も殺してくれたら万々歳かなと思ってました。

原作の思い出

テレビで原作をみた記憶がうっすらと残っています。と、いっても内容はほとんど覚えていなかったのですが、このタイトルと、一緒に見ていた父親が「花火は球状に広がるんだから丸いに決まっている」とどや顔していて、幼い自分は「さすがとーちゃん!」と尊敬していた記憶がやけに残っています。いまからすれば、そんなわかりきったことを問いているわけではなく、このタイトルでそのような疑問をもつ、「思春期感」を引きだす絶妙な名前だったんでしょうか。 *1 *2

ストーリーは

前に書いたように原作はほとんど覚えていませんが、おそらく原作を踏襲しているものなんでしょう。正直、なんでだろう、という疑問が消えないことが多くて、ストーリ的には戸惑うことが多かったです。そもそもあのアイテムはなんだったのか。なんで主人公が選ばれているのか。大ヒット作「君の名は。」と対比させていただくと、君の名は。はぎりぎりだとしてもそうした現象の説明をきちんとしているのに対して、本作はその説明は全くない状態。後半初めまでもやっとした気持ちが消えることがありませんでした。

じゃあ悪かったのか

ということでもない。きれいな映像、音楽、そして主役2人の絶妙な演技もあいまって、戸惑い中がらもこの世界観に少しづつ引き込まれてしまいました。ロジックで感じるのではなく感覚で感じていくものかもしれません。 おそらくは私の精神状態も関連していたのかもしれません。中二病を完治したくて見に行ったのにも関わらず、治すというより、「悪玉中二病」が浄化されて「良玉中二病」が残ったイメージです。そうです。「中二病でもいいんだ!」「おめでとー!」「おめでとう!」っていう気分になりました。これからもこの子を飼いならして生きていこうと思った所存です。 これで私の夏がようやく終わりそうです。来年も素敵な夏を迎えられればいいと思いました。

おいおい、これ映画の感想になってねーぞw

*1:また、この作品をとりあげた、「if もしも」という番組も覚えていたのですが、「打ち上げ花火ー」がこの番組の一作品という記憶はありませんでした。記憶が正しければ、「世にも奇妙な物語」のレギュラー放送の後番組として、タモリが司会をしていたような気がします。

*2:ちなみにこの原作、だいぶフェチ満載なものだったようで、よくそんな作品、あの父親と一緒に見れてたと感慨深いものがあります。

失格旅人という言葉を知った話

headlines.yahoo.co.jp

このネタに関してちょっと前に経験した話。 台湾支社、台湾人の仕事仲間が休みに家族で北海道のオートキャンプ場を回るツアーを計画していて、お願いされて予約を手伝ってあげたことがあった。とある北海道のキャンプ場に予約の電話をした際に、「利用者は台湾人で日本語が話せない(英語はOK)」と伝えたとたん問答無用に拒否られた。そのおっちゃん曰く、「以前、台湾からの客を受け入れてトラブったことがあり、言葉も通じず困った」とのことだ。

その台湾人はそんな場所で迷惑をかける人では決してないことは自信もって言えるのだが、そのおっちゃんは知らないことだし、面倒の種を事前に回避したいとうことだろう。それこそインバウンド観光産業を盛り上げていかない北海道の観光地なのに、こうしたところのカバーがまだまだできていないことが感じ取れた。そのキャンプ場の規模はわからないが、中国語しゃべれる人材一人雇うのも大変かもしれない。。。*1

私も大バカ者で、今考えれば適当な嘘をつくべきだったが、そのまま台湾人に伝えてしまった。やはりショックを受けてしまい、「わたしは「失格旅人」ではない。」(英語のやり取りにこの漢字が混ざっていた)とコメントした。

気になってこの言葉を調べた。ニュアンスだけだがおそらく、台湾人内で特に日本旅行などでマナーを守らない旅行客のことをこういうようだ(間違っていたらすいません)。こうした同国の人を叱責する言葉ができるぐらい台湾では旅行でのマナーを気にし、よい旅人でありたいという人が多いということだろう。そんな人たちの意をくむ意味でもなんとかお互いがハッピーになれるような策があってほしいものだと強く感じる。

*1:ちなみに、ほかのキャンプ場では同じ条件ですんなり受け入れてくれた。英語を話せるスタッフもいないようだったが、全然Welcomeな雰囲気を出してくれた。当然だが、受け入れるかはキャンプ場による。

反復性肩関節脱臼 術後三か月

手術後3か月たちまして。

リハビリを週2ペースでいっております。リハビリは手術をした病院ではなく、町の整形外科院に通っています。その病院はおそらくあまり私のようなケースを扱うことは珍しかったのか、当初は戸惑っていたようにはみえますがいろいろ献身的にサポートをしてもらっています。おかげでかなり上のほうまで腕が上がるようになりました。

ただ、前回の手術をした総合病院の検診をしたところ、可動の幅がもう少しあっ他方がいい。もし次回の検診ですすみがよくないようならば筋肉を緩める注射をしたほうがよいとのこと。

もう少しがんばっていきたいなと思います。

bython-chogo.hatenablog.com

bython-chogo.hatenablog.com

ジョジョの奇妙な冒険 ダイアモンドは砕けない 第一章

久しぶりに映画観ました。ジョジョの実写化映画。しかし最近はジャンプ漫画の実写化が多いですね。人気作品は認知度があるからある程度の収益が認められて、予算が落ちやすいということなのでしょうか。

f:id:bython-chogo:20170812005125p:plain

映画『ジョジョの奇妙な冒険 ダイヤモンドは砕けない 第一章』公式サイト

実写化されるのは原作のジョジョの第四部、日本の杜王町を舞台に東方仗助を主役にスタンドバトルが繰り広げられたジョジョ内でも人気のある部。個性あふれるスタンド使いたちが登場し、「力の強さ」だけではなく、頭脳戦とチームワークで敵スタンド使いを倒していく熱いバトルが持ち味となっている。

勝手な推論を続けさせてもらうと、実写化に当たり、4部を選んだのは「ギリ実写化可能」な舞台だったからといえるのではないだろうか。すべての始まりの一部や、一番人気の三部は実写化するにはあまりにも現実味をもった絵作りが難しそうである。また3部は長旅を続けていろいろな国の人が登場するため、旅前提により映画の尺と物語の進行度の兼ね合いも難しそうだ。その点4部ならば、舞台は日本の一都市(杜王町仙台市がモデル)で、進行度の調整もあるていど自由にできそうだ。日本人に親近感があり、さらに実写化しても比較的リアリティ感が保てそうな4部を選ぶのは当然かもしれない。

しかしながら、日本の一都市というのはあまりにも観客に身近すぎるため、登場人物が浮きすぎてしまう。漫画の登場人物の姿格好に似せようとすればするほど、現実世界ではとても奇抜な方たちになってしまうのだ。ジョジョパラドックスといま思い付きで名付けた。製作者サイドがこのリアリティラインの調整を頑張っている様子はすごく感じ取った。例えば杜王町の町の感じは劇中で「日本の一都市」と堂々と語らせているにはあまりにも欧米的な雰囲気が強い。がちがちな日本の都市感を背景にしてしまうとリアリティラインが上がりすぎて、登場人物とのギャップが激しくなってしまうことを防いだと考えられる。

そんな努力はみられるのだが、残念ながら登場人物のういている感じはぬぐいきれない。例えば仗助が髪をからかわれてブチ切れるというシーケンス。これは4部にはなくてならないものだと思うのだが、残念ながら登場人物ほとんどの髪型は(原作に忠実に再現しているため)、おかしい。もっとからかいたくなる髪型のやつはたくさんいるどころか、最初にからかっているヤンキーですら「おまえがいうな」状態になってしまっている。

もう一つ、すこし残念だったのはスタンドのCGがどうもゴム人形や特撮の着ぐるみみたいに見えてしまった。なんかちょっと安っぽい。これは現実に形状の「スタンド」を現実空間に押し込んだためにでるギャップで解決策はないのかもしれない。

文句ばかりではなく良かった点をあげると、個人的にクレイジーダイアモンド v.s. バッドカンパニーはちょっと燃えた。これは見れてよかったかなと思いました。

今作は第一章となっていますが、今後何章まで続けるのかは不明ってことでしょうか。今回、バトルははじまっていないですが、由香子、露伴、鈴美、(トニオも?)あたりの伏線もいろいろ込めてくれたようです。不安なのが観客動員数によって無理やり幕引きとかそういう事態はファンとしては望まないです。また、しょうがない、ということは重々承知の上、一言。 レッドホットチリペッパー省いたかー!ザ・ハンドでざっくり削り取られたんかー! ある程度まとまりをつけるためには個性的な4部キャラの(泣く泣くの)選定は必要になってくるでしょう。そのあたりの采配も期待をしておりますので、次回もなにとぞよろしくお願いいたします。

異常検知と変化検知 - 第3章 k近傍法

モチベーション

この本読み始めました。専門知識とか、数学の基礎知識が多く私が理解するにはちょっとハードルが高めだったのですが、Pythonで可視化して、どのようなアルゴリズムなのかを理解しながらゆっくり進めたいと思います。このエントリーでは 3章の「近傍法による異常検知」から k近傍法を可視化したうえで方法を説明できればと思っています。 アルゴリズムの詳細をかききるのは難しいかと思いますので、気になった方は本を参照いただければと思います。

k近傍法のサマリ

簡単な例として、異常と正常のラベルがついた (x, y) の二次元のサンプルがあるとします。サンプルそれぞれの点から k 個の近い点をチェックしてその正常値、異常値が含まれる割合を求めて、異常値と判断する割合の閾値を計算します。その後、新しい値が来たときに同じような割合の計算をして設定した閾値を超える場合はAlertを出すようにします。

詳細は本書と、外部ですが以下のスライドがわかりやすいかと思います。 異常検知と変化検知 第4章 近傍法による異常検知

今回は正規分布をランダムにだす numpy の関数 randn() を使ってサンプル値を作成します。

num = 100 # 正常値の数
inum = 5 # 異常値の数
# randn()で正常値を 100個、異常値を中心をずらして5個 設定
smpl = [[randn(), randn(), 0] for i in range(num)]
smpl += [[randn()*0.4 + 2.0, randn()*0.4 + 2.0, 1] for i in range(inum)]

図示すると以下のようになります。 f:id:bython-chogo:20170624200213p:plain

参照数 k と 閾値 F値 をサンプルより計算する

少し驚いたのですが、サンプルから最適な参照する近傍数kと閾値であるF値が高くなるa_thの値を変更しながら一番最適な閾値を見つけるのが事前にやる作業になります。これが経験分布ってことで正しいのでしょうか。そのあたりわかっていません。

kの候補の値を1~6まで、a_thの候補の値を0~5まで0.1まで細かく確認して、F値を計算します。

        klist = [1, 2, 3, 4, 5, 6]
        athlist = [0.1*(1+i) for i in range(50)]
        # 現在までの最大値を記録 [k値、a_thの値、最終的な閾値F値]
        maxset = [0, 0, 0.0]
        for kv in klist:
            for av in athlist:
                self.k = kv
                self.ath = av
                tmp_set = [kv,av,self.calc_ath()]
                #print "k, ath, f", tmp_set
                if tmp_set[2] >= maxset[2]:
                    maxset = list(tmp_set)

閾値を比較するためにのF値の計算は calc_ath()の関数で計算します。F値に関しては一章に書かれてますので、ご参照いただければと思います。

k 値と a_thの値から導き出された F値をプロットしてみます。 f:id:bython-chogo:20170624190238p:plain ちょっとわかりずらいからと思いますが、k=4の時のF値が 0.3636…と一番高い値になります。このF値が基準として、異常値の判定をおこないます。

F値が超える範囲にいろをつけてみた

参照する近傍数K=4で設定して、0.3636を超える場所を薄赤で表示してみます。

    def plot_abn(self):
        k, ath = self.check_better_comb()
        print k, ath
        xl = []
        yl = []
        
        # 縦横-3.0 から 3.0まで0.1でF値を計算してプロット
        for x in range(60):
            xd = x*0.1 - 3.0
            for y in range(60):
                yd = y*0.1 - 3.0
                dltList = []
                # 各地点とサンプル間の距離を計算
                for j, smp in enumerate(self.smpl):
                    dltList.append([self.delta(smp[0:2], [xd, yd]), j])

                nml, abnml = 0, 0
                n_cor, ab_cor = 0, 0
                # 近い順に k 個のサンプルを抽出してF値を算出。
                # 閾値を超えた値をxl, ylに記録
                for j, dl in enumerate( sorted(dltList)):
                    if j == k:
                        break

                    if self.smpl[dl[1]][2] == 1:
                        abnml += 1
                    else:
                        nml += 1

                if np.log(self.pi0*abnml)-np.log(self.pi1*nml) > ath:
                    xl.append(xd)
                    yl.append(yd)

        plt.xlim(-3.0, 3.0)
        plt.ylim(-3.0, 3.0)
        plt.scatter(xl, yl, color='r', alpha=0.1)
        self.ploting()
        plt.show()

表示すると以下のようになります。異常値である赤の周りが異常値として判定されるいるのがわかります。多少正常値のサンプルも入ってきますが、アラートを上げる範囲としては妥当といえるかと思います。 f:id:bython-chogo:20170624190618p:plain

ソースコード

以下、今回利用したソースコードになります。ご確認を

import numpy as np
import matplotlib.pyplot as plt
import random
from numpy.random import *
%matplotlib inline

num = 100
inum = 5
smpl = [[randn(), randn(), 0] for i in range(num)]
smpl += [[randn()*0.4 + 2.0, randn()*0.4 + 2.0, 1] for i in range(inum)]

class KNearest():
    def __init__(self):

        self.num = num
        self.inum = inum

        self.pi0 = self.num*1.0/(self.num+self.inum)
        self.pi1 = self.inum*1.0/(self.num+self.inum)
        
        self.k = 5
        self.ath = 0.1
        self.cand = []
        
        self.smpl = smpl

    # サンプルのPlot
    def ploting(self):
        plt.xlim(-3.0, 3.0)
        plt.ylim(-3.0, 3.0)
        for s in self.smpl:

            if s[2] == 0:
                plt.plot(s[0], s[1], 'bo')
            else:
                plt.plot(s[0], s[1], 'ro')

    # 2地点の距離の計算
    def delta(self, a, b):
        return (a[0]-b[0])**2 + (a[1]-b[1])**2

    # a_thからF値計算。。。データ消してしまった orz..後ほど
    def calc_ath(self):

    # 最適なF値の計算
    def check_better_comb(self):
        klist = [1, 2, 3, 4, 5, 6]
        athlist = [0.1*(1+i) for i in range(50)]
        
        maxset = [0, 0, 0.0]
        for kv in klist:
            for av in athlist:
                self.k = kv
                self.ath = av
                tmp_set = [kv,av,self.calc_ath()]
                #print "k, ath, f", tmp_set
                if tmp_set[2] >= maxset[2]:
                    maxset = list(tmp_set)

        return maxset[0], maxset[2]

    # F値のプロット
    def plot_better_comb(self):
        klist = [1, 2, 3, 4, 5, 6]
        athlist = [0.1*(1+i) for i in range(50)]
        
        result = {}
        maxset = [0, 0, 0.0]
        for kv in klist:
            result[kv] = []
            for av in athlist:
                self.k = kv
                self.ath = av
                result[kv].append(self.calc_ath())

        for kv in klist:
            plt.plot(athlist, result[kv], label='k =' + str(kv))
        plt.legend()
    
    # F値を超越して異常と判定される範囲をプロット
    def plot_abn(self):
        k, ath = self.check_better_comb()
        print k, ath
        xl = []
        yl = []
        
        
        for x in range(60):
            
            xd = x*0.1 - 3.0
            for y in range(60):
                yd = y*0.1 - 3.0
                dltList = []
                for j, smp in enumerate(self.smpl):
                    dltList.append([self.delta(smp[0:2], [xd, yd]), j])

                nml, abnml = 0, 0
                n_cor, ab_cor = 0, 0
                for j, dl in enumerate( sorted(dltList)):
                    if j == k:
                        break

                    if self.smpl[dl[1]][2] == 1:
                        abnml += 1
                    else:
                        nml += 1

                if np.log(self.pi0*abnml)-np.log(self.pi1*nml) > ath:
                    #print xd, yd
                    xl.append(xd)
                    yl.append(yd)

        plt.xlim(-3.0, 3.0)
        plt.ylim(-3.0, 3.0)
        plt.scatter(xl, yl, color='r', alpha=0.1)
        self.ploting()
        plt.show()