2017-01-08[n年前へ]
■ネット画像から有名人の個人情報(静脈パターン)を可視化してみよう!?
SNSなど投稿した「ピースマークなどの指写真」から指紋を推定する記事「指紋がネットで狙われている! 手の画像は悪用恐れ… 国立情報学研が新技術の実用化目指す」を読みました。写真の解像度が高ければ、指紋の抽出は確かにできそうです。もっとも、2017年時点の、よくある撮影解像度やアップロード画像解像度を考えると、指紋抽出はまだ困難な気がします。
撮影の解像度だけを考えれば、皮膚内での波長毎の吸収・散乱特性の違いによる静脈可視化の方が楽かもしれません。掌を走る静脈は、その模様が人ごとに違うため、個人認証機器に多く使われたりします。そこで、各波長(RGB画像)の特性の違いを利用して、静脈推定をしてみることにしました。
まずはネット上から掌が映った写真を探してみることにします。有名人の掌が映った写真はとても多く、観客に手を振る写真・演説中に両手の掌を開いて聴衆に見せている写真など、さまざまなパターンの画像が手に入ります。
そして、静脈を可視化するために色の違いを利用するとなると、重要なことは「圧縮率が低い画像を探す」ということです。なぜかというと、人の視覚系は明度に対しては敏感ですが、色の違いに対しては解像力が低いため、多くの画像ファイルでは色情報が圧縮され低解像度になっているからです。
圧縮率が低く・掌が映った有名人画像を探してみると、まず最初に見つかったのが綾瀬はるかさんの写真です(下左図)。この画像に「静脈部分を抽出する画像処理」を掛けてみると、皮膚下を走る静脈が浮かび上がってきます(下右図)。
静脈模様を可視化する用途には、ネットにアップロードされている画像では圧縮率が問題になります。しかし、自分で撮影する画像なら、撮影さえすれば、圧縮が掛かっていない画像が手に入ります。…ということは、個人情報を狙われやすい有名人の人は、静脈模様を覆い隠す「静脈盗撮防止用のコンシーラー」などが必需品になるかもしれません。
2017-01-14[n年前へ]
■ライトフィールドプリント最適化のためのBlenderシミュレーション その1
いくつもの視点から見た映像から視点変化に応じた見え方を再現するライトフィールド・プリント(インテグラル・フォトグラフィー)を作るために、Blender を使った透明シートへの印刷シミュレーションをしながら、効率的な印刷方法に挑戦してみた。
どんな場合でも汎用的に使うことができる一般的な画像に対して、視点変化に応じた見え方を再現しようとすると、偏光素子を重ねるなど特殊な方法を使うのでなければ、視野を制限するマスク(アパーチャ)に相当する層を上にプリントした上で、視野変化に応じて見える光(色)をさらにその下にプリントすることになりそうだが、そうすると、次の問題が生じる。変角解像度を両立させるためにはマスク(アパーチャ)を小さくしたくなるが、そうすると画像が暗くなる。
この問題に対して最適解を出そうとすると、マスクの口径(アパーチャー)が持つ周波数特性を、視点変化に対する見え方変化の周波数特性と一致させたくなる。つまり、もしも、視点が変化しても見え方の変化が小さい方向があれば、その方向に対してはマスクの口径(アパーチャー)を大きくし(周波数特性を鈍くし)、視点変化に対して見え方が敏感に変わる方向があれば、その方向に対しての口径を狭くしたくなる。つまり、高周波が鈍ることを防ぎたくなる。
というわけで、ライトフィールドプリントの上面に配置したマスク(アパーチャ−)形状を、視点変化に対する見え方の変化(ライトフィールド勾配)に連動した方向性を持つ楕円形状にした画像生成をするPythonコードを書いてみた。そして、スタンフォードの The (New) Stanford Light Field Archiveをサンプル画像として使い、Blenderでライトフィールドプリントのシミュレーションをしてみたのが下の動画だ。
眺めてみると、マスクサイズを固定とした範囲では、明度と変角解像度という相反する項目の両立が改善したような気もするが、空間方向変化(空間解像度)と連成した処理になっていないこともあり、まだまだ修正すべき項目が多い。…というわけで、次は、変角解像度と空間解像度の最適化を考えてみることにしよう。
2017-01-15[n年前へ]
■データで眺める誕生日、生まれる人が少ないのは「土日祝日と○月×日」
データで眺める誕生日、生まれる人が少ないのは「土日祝日と○月×日」を書いた。人が生まれる誕生日時は、平均的に眺めてみれば、比較的精度良くコントロールされているものだ。
データで眺めてみると、生まれる人が少ない「土日祝日と4月1日」と比べて、普通の平日は2割〜3割くらい生まれる人が多くなっています。誕生日はその年のカレンダーと連動しているので、誕生した人が多い日・少ない日は、年によって偏りが生まれます。ちなみに、休みとして月日がいつも決まっている祝日は、いつの年であっても「生まれた人は少ない」ということになるので、色々な年で平均してみてもやはり誕生日には偏りがあることになります。
2017-01-21[n年前へ]
■「入金」を意味する「チャージ」の歴史 第1話
現代の日本では、「入金する」ことを「チャージ」と表すことが多い。たとえば、何かを買ったり・使ったりすることができる権利や口座など、あるいは、そういう用途のカードに再入金を行うことを「チャージ」と表現することが多いように思う。
ところが、それを英語で表現しようとすると、それは「チャージ」ではない、というワナがある。「再入金する」ということを表すために、top-upとかadd valueとは言うけれど、chargeと言ってしまうと「課金する」「支払いを(さらに)求める」といった意味になってしまう。つまり、意味が逆になってしまう。
この「意味の逆転」が生じるに至った歴史・主要因として、まず一番疑いたくなるのがJR東日本が2001年から提供している乗車カード・電子マネーサービスであるSuica(スイカ)だ。それまでに、再入金できないプリペードカードはすでに広まっていたが、Suica(スイカ)は、再入金可能かつ広まったプリペードカードの先駆けだった。
その「Suica(スイカ)サービス開始」のプレスリリース、2001年9月4日にJR東日本が出したプレスリリースでは、カードに再入金をすることを「チャージ(ご入金)」と表現している。もっとも、この時代の日本では、入金=チャージという共通認識は確立されていなかったようで、「チャージ」には「チャージ:Suicaのイオカード部分に入金すること」という、注釈も付いている。今や「チャージ」という日本語に注釈が付くことは少ないが、この時代には注釈がまだ付いていた。ちなみに、イオカードというのは、SUICAカード以前に使われていた磁気式プリペイド乗車カード(に相当する機能)のことである。
もうじき20年近く前になる21世紀の初頭の日本、JR東日本が「なぜ、再入金することを表すために、チャージという言葉を選んだ経緯・理由」をJR東日本に(もしその経緯などを示す資料などあれば?と)問い合わせてみた。そして、JR東日本から頂いた回答は次のようになる。
明確な資料等はございませんが、Suicaにつきましては、従来の磁気式のイオカード等、使い切りの形ではなく、充電式の電池等のようにカードに再度入金をしていただくことで、1枚のカードを繰り返しご利用いただけることをお客さまにわかりやすくお伝えする趣旨で充電等を意味する「チャージ」の用語を用いております。なお、一部、わかりづらいというご意見がありましたので、宣伝物等のご案内については「入金(チャージ)」という表現を使用しております。
JR東日本
CHARGEの語源は、ラテン語の「荷車(carrus)」から、「荷を積む(carricare)」という意味が、古フランス語を経て、英語で使われるようになったものだ。荷を積むことは、負荷を掛けることであって、それは支払いを求める(責任や仕事の分担を求める)ことにも繋がるし、充電された状態にするための「負荷」を掛けることも意味する。この車に積む「荷」をプラスの(足す)作用を持つ存在と捉えるかマイナスの(引く)ものと考えるかが、2017年の日本語のチャージと英語のChargeの違いに繋がっているように思われる。
JR東日本が「チャージ」という言葉を選ぶに至った歴史の前ページ、そして、それ以外のエトセトラ…については、次にまたメモ書きしてみたいと思う。
2017-01-29[n年前へ]
■「手持ちスマホ撮影動画からの超巨大開口レンズ撮影」に挑戦してみよう!?
かつて、スマホに搭載されているカメラのレンズはとても小さく、綺麗なボケとは無縁の存在でした。しかし、今や最新のスマホには特殊処理によるボケ生成機能などが備えられています。カメラレンズの光学開口径が小さくとも、たとえば2眼カメラなどを備えて距離情報を取得して、距離情報などからボケを人工的に合成するといった仕組みです。
そんな最新スマホを持たずとも、大レンズのボケ味を手に入れるために、「手持ちスマホ撮影動画からの超巨大開口レンズ撮影」に挑戦してみました。スマホ動画から巨大開口レンズ撮影の手順はとても簡単、まずは目の前の風景にスマホを向けて・なるべくスマホが平面上を動くように意識しながら(スマホを動かしつつ)動画撮影します。そして、動画の各コマから画像ファイル群を生成し、それぞれの画像が撮影された位置や方向にもとづいて撮影された光情報を加算合成する、というものになります。
細かい手順は、スマホ撮影動画(の展開画像をもとに) Bundler: Structure from Motion (SfM) から出力された刻々のカメラ位置・方向や特徴点情報ファイル(bundler.out)を読み込み、それらのカメラ情報にもとづいて、刻々の撮影画像をレンズ開口面に沿った(同じ方向を向く平行カメラが存在していた場合の)光線画像を位置・角度ズレを踏まえて重ねることで、任意のピント位置に焦点を合わせた超巨大開口レンズ撮影画像を生成する…というものです。
試しに、iPhoneを約1.5m×1.0mの範囲で動かしつつ動画撮影し、つまり、レンズ直径約1.5mに相当する範囲で動かしつつ動画撮影し、その画像群から開口合成により超巨大カメラの撮影画像を作り出してみた結果が右上の画像です。
右上画像を眺めてみても、良好なボケ味どころか、全くピントが合っていない画像にしか見えません。直径が1mを超える開口を持つカメラレンズとなると焦点深度もとても浅くなるのでピントがなかなか合わない…というわけでなく、手持ち撮影動画からのカメラ位置・方向精度が低いせいか、単一カメラに平行合成した後のズレが大きいようです。
ちなみに、試しに各画像を(撮影方向による傾きを補正しつつ)位置毎に並べてみると、下の画像のようになります。動画撮影からのカメラ位置推定精度が果たして不十分なのかどうか、次は撮影カメラ位置を精度良く知る事ができる撮影治具でも作り、また再挑戦してみたいと思います。
上記処理のコード手順、Python/OpenCVで書いたコード処理手順は、bundler.outからカメラ位置・方向・焦点距離や歪みパラメータを読み込み、cv2.initUndistortRectifyMapにカメラ情報を渡して、各撮影画像の向き補正用のホモグラフィーマップを作成してremapで変換した後に、各撮影画像を加算合成するという手順です。
Bundlerの出力ファイルを読み込んでライトフィールド合成を行うOpenCV/Pythonコード、まだまだ間違い含まれているような気もしますが、とりあえずここに貼り付けておくことにします。
import cv2 import numpy as np from matplotlib import pyplot as plt from PIL import Image import math %matplotlib inline class camera: def __init__(self): self.f = 1.0 self.k1 = 0.0 self.k2 = 0.0 self.R = [[0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]] self.T = [0.0,0.0,0.0] def readBundlerOut( filePath ): f = open(filePath, 'r') list = f.readlines() f.close() numberOfCameras = int((list[1].split())[0]) cameras = [] for i in range(numberOfCameras): aCamera = camera() fk1k2 = [float(j) for j in list[5*i+2].split()] aCamera.f = fk1k2[0] aCamera.k1 = fk1k2[1] aCamera.k2 = fk1k2[2] rot = [] for j in range(1,4): rot.append( [float(k) for k in list[ 5*i+2+j ].split()] ) aCamera.R = rot aCamera.T = [float(k) for k in list[ 5*i+2+4 ].split()] cameras.append(aCamera) return cameras def readImageList( listPath, imageDirPath ): f = open(listPath, 'r') list = f.readlines() list = [ i.rstrip() for i in list ] f.close() list = [imageDirPath+fileName for fileName in list] return list class lightField: def __init__(self): self.w = 2000 self.h = 2000 def loadImageListAndMakeLightField( self, imagePathList, cameraList, workList, scaleA ): self.cimg = np.zeros((self.h, self.w,3), dtype=np.uint8) sum = 1.0 for i in workList: img = cv2.imread( imagePathList[i], cv2.IMREAD_COLOR ) h, w = img.shape[:2] imageHeight = img.shape[0] imageWidth = img.shape[1] focalLength = cameraList[i].f principalPointX = 0.500000 principalPointY = 0.500000 distCoef = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0 ]) cameraMatrix = np.array([ [focalLength, 0.0, imageWidth * principalPointX], [0.0, focalLength, imageHeight * principalPointY], [0.0, 0.0, 1.0] ]) newCameraMatrix, roi = cv2.getOptimalNewCameraMatrix( cameraMatrix, distCoef, (img.shape[1], img.shape[0]),1, (img.shape[1], img.shape[0]) ) rotMatrix = np.array( cameraList[i].R ) map = cv2.initUndistortRectifyMap( newCameraMatrix, distCoef, rotMatrix, newCameraMatrix, (img.shape[1], img.shape[0]), cv2.CV_32FC1) undistortedAndRotatedImg = cv2.remap( img, map[0], map[1], cv2.INTER_LINEAR ) scale = 1.0 pt3 = np.array(cameraList[i].T) - np.array(cameraList[0].T) x = ( self.w/2.0 - pt3[0] * scale * scaleA ) y = ( self.h/2.0 - pt3[1] * scale * scaleA ) pts1 = np.float32( [[0, 0], [w, 0], [w, h], [0, h]]) pts2 = np.float32( [[x, y], [x + w*scale, y], [x + w*scale, y + h*scale], [x, y + h*scale]] ) M = cv2.getPerspectiveTransform( pts1, pts2 ) img2 = cv2.warpPerspective( undistortedAndRotatedImg, M, (self.w, self.h) ) sum = sum + 1.0 self.cimg = cv2.addWeighted( self.cimg, (sum-1) / sum, img2, (1.0) / sum, 0) def showImage(self): plt.figure( figsize=(14,14) ) plt.imshow( np.array(self.cimg2) ) plt.autoscale( False )