ガジェットリスト更新

f:id:nobu_macsuzuki:20210115110857j:plain

これでGarmin Forerunnerは3本目。
225 -> 235 -> 245だ。
225は確か心拍計の誤動作が増えた(まったく検知しない)ために買い替えたと記憶している。
235も心拍計の誤動作はしばしばあったのだが、再起動するとほぼ治るのでだましだまし使っていた。
しかし、ここ数週間走った後にBluetoothの接続不良を繰り返したため買い替えた。
もう、Garmin Forerunnerは消耗品だと思って、2年に一度買い替えよう。


さて最新版だが、だいぶ導入過程が洗練されてきて、Garminらしからぬ。
電源を投入すると、スマホとの接続要求の画面がでて、iPhoneGarmin Connectから「デバイスの追加」を選択すればおしまい。
このころがちょっと懐かしい、なんて言わないわ、Fake It!

  • Nokia 5.3(現地回線)
  • iPhoneSE (2nd Gen, 日本回線)
  • EOS Rp
  • Cube PC(Core i7 7700)
  • MacBook Pro 13" 16-2(Mid 2020 A2251, Core i5 1038NG7)
  • Chuwi Hi10 XR (Geminilake Reflesh) - FX選定評価中
  • Magic Ben MAG1 (Core m 8100Y)
  • Samsung Galaxy Tab S5e 64GB
  • Garmin Forerunner 245
  • Fossil Gen 5 Carlyle
  • Bose Quiet Comfort 35

forループでループカウントを得る

enumerateを使う、整数のループカウントと配列もしくはリストの要素がtupleで得られる。

    arrayTest = np.array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], dtype=float)
    for (count, element) in enumerate(arrayTest):
        print(count, element)
    for (count, element) in enumerate(arrayTest[1:10:2]):
        print(count, element)

docs.python.org

github.com

一年の計は元旦にあり

一年前に全く同じタイトルでブログを書いている、自分の進化のなさがあまりに香ばしい。


まずは昨年を振り返ってだが、多すぎるノートPCの整理はだいぶうまくいった。
Surface Proは大幅リストラ、全て手放した。
MacBookはパフォーマンスを上げ、容量を上げ、立派なお座敷コンピュータになった。
一方、コロナの影響もありUMPCは極めて稼働率をさげ、一方で次期支援戦闘機の導入の検討を始めた。
目下のところはこのMacBook ProとFXことChuwi Hi10 XRが専ら稼働中だ。


次に携帯電話、「行かないでくれー!今までもお前は最高だったし、これからも最高だー!」Blackberry Key2亡き後、迷走中だ。
今年はこの更新、整備が課題になるだろう。
目下は古巣のNokia5.3で糊口をしのいでいるが、QWERTYキーボードがないため、タイプスピードの低下、タイプミスの増加はいかんともし難い。
F(x)tec Pro1を再発注してあるのだが、2020年9月に注文して発送予定は2021年3月と相変わらずkonozamaだ。
実はPlanet ComouterのAstro Sliderも予約済みだ。
この2台は横広使いなので、Android OSではめちゃくちゃ使いにくいことは明白なのだが、キーボードがないよりはましだ。

と、思っていたら、"Blackberry Key 3"のニュースが飛び込んできた。
TCLが出すのか、はたまたTXのうさんくさい会社がFIH(Foxconn系の携帯電話EMS、ノキアのブランドライセンスを受けているHMDの生産もやっている)と組んで出すのか、どっちでもよいので、続報期待!


あとは・・・なにかあんのかねぇ・・・

米国の年末にかけての行事

久しぶりにこのトピックの投稿だ。
筆者のいる太平洋岸北西部(Pacific Northwest、PNWとも呼ばれる)は、日本と同じようにしっかりと四季が分かれている。
日本の暦とは異なり、ニュース番組などを見ていると、
春: 春分 - 夏至
夏: 夏至 - 秋分
秋: 秋分 - 冬至
冬: 冬至 - 春分
と、太陽の運行で季節を分けているようだ。
なので年末にかけての行事、というとおおよそ秋から冬の始まりの行事になる。


まずは10/31のハロウィン、ハロウィン - Wikipedia
これは完全に秋祭り。
仮装をして街に繰り出すイメージがあるが、繰り出すのは主に小学生までの子供。
手にかぼちゃのおばけ(Jack-o'-Lanternという、後で記述)の形のバケツを下げ、昼間は親の職場やショッピングモール、夜は近所を練り歩いてお菓子を貰って歩く。
いや、本来は「Trick or Treat」なのだが、その可愛い姿におひねりをお姉さま、おば様方がばらまく、というのが正確。
仮装はディズニーやニンジャレンジャーなどのこちらで人気のアニメのキャラ、ちょっと大きくなるとハリーポッターなど映画のキャラも多い。
ハロウィーン当日は学校に仮装して登校することが許可される、息子も趣向を凝らした仮装をして高校に登校したこともある。

家の前には大きなかぼちゃをくりぬいて作った提灯、Jack-o'-Lanternを飾る。
ちょっとした飾り、例えばおばけ、を家の前に置く。
この辺りから大人がエスカレートする。
家をお化け屋敷に飾る。
家全体に大きな白いネットをかけ、屋根には大きな蜘蛛、家の前には墓石が並び、夜には紫や青、緑のおどろおどろしい電飾のプロジェクション。
コミュニティ単位でこれをやって、お化け屋敷通りができるところもあり、そんな中を子供たちが練り歩く。
職場でもノリノリで仮装して出社する人も、特に女性を中心に、ある。
若い人は仮装をして、バーやクラブに繰り出して、深夜まで遊び倒すー日本ではこの部分が一番メジャーなようだ。


次は11月の第4木曜日の感謝祭、Thanksgiving、感謝祭 - Wikipedia
これは勘違いされることもあるが、キリスト教のものではなく、北米だけの習わし。
北米の入植者の故事にまつわるものだ。
で、実際のところは日本の正月に一番近い。
遠くに住む人も実家に家族連れで帰り、親戚ともども食事をして、一緒に時間を過ごす。
料理は七面鳥、ターキーだ。
丸の七面鳥に、細切れにしたパンを詰めて長時間オーブンで焼いたものがメイン。
これに同じくオーブンで調理した野菜を添えて、クランベリーソース、七面鳥から出た油を元にして作るグレービーソースをかけて食べる。
当日はアメフト(NFL)の大きな試合があるので、これを家族そろってみるのも大切な風習だ。
そして日本の正月同様に、子供たちはひまで時間を持て余す。
感謝祭の翌日はご存知、Black Friday
NFLの試合を見たら、近所のWalmartCostco、Best Buyに並んで、深夜オープンで争奪戦だ。

この感謝祭の週が米国で最大の民族大移動の週になる。
上でも書いた通り、遠くに住んでいても実家に戻って家族と時間を過ごすのだ。
日本同様に、「今年は帰ってくるのよね!」とおかぁちゃんから電話がかかってくる。
感謝祭の週全体が実質的に休み(日本のお盆のように休暇を消化する週)になる職場も多く、そうでなくても水曜日は半ドンなところも多い。
水曜日の午後の空港は満杯。
日本に住んでいる頃に、何も知らずにこの日に出張のフライトを組んでしまったことが一度あったが、3時間ぐらい大行列とえらい目にあった。
感謝祭翌日の金曜日に11/11の退役軍人の日を振り替えて、休みにする会社も多い。
そして週末にはまた混雑の中、自宅へ戻っていく。
そしてその疲れと共に月曜日に出社したら、会社の高速インターネットでネットショッピング、というのがCyber Mondayの興りだ。


この感謝祭明けから、世の中は一気に年末、クリスマスムードになる。
日もだいぶ短くなり、PNWは天気が雨がちになる、冬の到来だ。
街はクリスマスのデコレーションでいっぱい、町の広場には巨大なツリーが飾られる。
ゴスペルのクリスマス・ソング・コンサートや、くるみ割り人形のバレーがあちこちで上演される。

クリスマスはご存知の通り、イエス・キリスト降誕祭だ。
中国語では「聖誕祭」というらしい。
ご存知の通り、キリスト教国では東アジアのように「彼女と甘い時を過ごす」ものではない。
宗教色を抜いて考えれば、家族・親戚・知人とプレゼント交換をする、いわばお歳暮だ。
大きなクリスマスツリーを家の中に据え、その下にきれいに包装されたプレゼントを山積みにする。
これはもらったもの、これから他人にあげるものを飾っておくのだ。
米国ではお店で何か買ってもきれいなラッピングはしてもらえない。
なので、Michaels'やJoannといったおば様御用達の手芸ショップにいって、包装紙やらリボンやらを山のように買ってきて、片っ端から自分で包むのだ。
包装が面倒、不器用でできない、という向きにはきれいな紙袋があるので、これに入れてちょっとデコる。

さて、クリスマスはいつ?
日付は12/25だが、クリスマス・イブとは「クリスマスのイブニング」という意味だ。
昔は一日は夜から始まったので、今のカレンダーでいう12/24の夕暮れがクリスマスのスタートだ。
12/24の夜には教会でクリスマス・ミサがあるのでそれに列席する。
子供はサンタクロースを待たずにとっとと寝る。
翌日はプレゼントの日だ。
子供たちは早起きして、自分あてのプレゼントと、そしてサンタからのプレゼントをばりばり開ける。
その日は家族で過ごし、夕食はハムステーキだ。

結婚している人の場合には、クリスマスを感謝祭で過ごさなかった方の実家で過ごすことも多い。
つまり感謝祭は嫁の実家、クリスマスは自分の実家、という感じ。
米国では父母が離婚していることも多いので、感謝祭は義父実母の実家、クリスマスは実父義母の実家、などということもある。

なので、山下達郎のクリスマス・イブが流れる中、どこぞにきれいな夜景を見に行くことはない。
恋人なサンタクロースが町から連れ去ってくれることもない。
クリスマス・イブにプレゼント交換はしない。
クリスマス・イブにケンタッキーフライドチキンがバカ売れすることもない。


これでお祭りはおしまい、米国なら12/26から平日、仕事も平常営業。
12/31-1/1はあっさり、なんの特別なこともない。
1/1は休日なのだが、もうたぶん、いちばん味気ない休日。
下手をすれば、お店も休業しない。
上でも書いた通り、家族の集まりは感謝祭なので、年末に集まることもない。
それこそ恋人と年越しの花火でも見て、甘い時を過ごすくらいか。
12/31も平日なら、1/2も平日なので、下手をすれば働く。

ファイルパス操作

githubを利用してソースコードを複数のマシンで共有していると環境依存の部分をできるだけ取り除いて、共通部分だけを共有できるようにする必要がある。
前回の画像認識で、OpenCVに含まれるHarr-likeを利用したカスケード識別機を使っているのだが、この識別機のリファレンスデータも一緒に配布されている。
これはインストールパス依存になるので、以下の方法で共通化した。
まずPythonの実行ファイルのパスを取得して、そこから実行ファイル名を取ってPythonのインストールパスを取得する。

    pathPython  = sys.executable
    pathPython = pathPython.replace('python.exe', '')

次にそれにhaar-likeのカスケード識別機のリファレンスデータパスを追加。
os.path.joinはC#でいうPath.Combineで、パスを示す環境依存文字を使わずにパスの接続ができる。
文字列の頭に"r"を付けるとエスケープシーケンスを無視するので、"\"を"\\"と書かずに済む。

    pathCascade = os.path.join(pathPython, r'pkgs\libopencv-4.0.1-hbb9e17c_0\Library\etc\haarcascades')

最後に顔や目などのそれぞれのデータパスを作る。

    pathCascadeEye = os.path.join(pathCascade, 'haarcascade_eye.xml')
    pathCascadeFrontFace = os.path.join(pathCascade,'haarcascade_frontalface_default.xml')

sysやosは標準ライブラリーではないので、

import sys
import os

をあらかじめインポートしておく必要がある。

OpenCVとnampy(ndarray)で画像処理

Pythonの醍醐味といえば、OpenCVなどの強力なライブラリーが使えることだ。
過去に自力でかなりひーこら言いながら実装したフーリエ変換(FFT)や射影変換があっという間にできてしまった。
OpenCVは中間データフォーマットとしてndarrayを使うので、ndarrayで直接操作もできる。

まず普通に開く。

    arrayImageColor = cv2.imread(filename, cv2.IMREAD_COLOR) ## cv2 returns ndarray of BGR orientation

f:id:nobu_macsuzuki:20210101024108p:plain
arrayImageColorは(512,512,3)のndarrayになる。

これをグレースケールに変換。

    arrayImageGray = cv2.cvtColor(arrayImageColor, cv2.COLOR_BGR2GRAY)

f:id:nobu_macsuzuki:20210101024210p:plain
arrayImageGrayは(512,512)のシングルチャネルのndarrayになる。

カラーの方を射影変換。

    offsetPerspective = 100
    (heightImageColor, widthImageColor) = arrayImageColor.shape[:2]
    arrayRectangleSource = np.array([[0, 0], [0, heightImageColor], [widthImageColor, heightImageColor], [widthImageColor, 0]], dtype=np.float32)
    arrayRectangleTarget = np.array([[offsetPerspective, offsetPerspective], [0, heightImageColor - offsetPerspective], [widthImageColor, heightImageColor - offsetPerspective], [widthImageColor - offsetPerspective, offsetPerspective]], dtype=np.float32)
    arrayMatrixPerspective = cv2.getPerspectiveTransform(arrayRectangleSource, arrayRectangleTarget)
    arrrayImagePerspective = cv2.warpPerspective(arrayImageColor, arrayMatrixPerspective, (widthImageColor, heightImageColor))

f:id:nobu_macsuzuki:20210101024335p:plain

グレーの方はFFT

    arrayFFTed = np.fft.fft2(arrayImageGray)
    arrayShiftedFFTed = np.fft.fftshift(arrayFFTed)

    arrayPowerSpectrumFFTRealPart = 20 * np.log(np.absolute(arrayShiftedFFTed))

    arrayRevertedShiftedFFTed = np.fft.fftshift(arrayShiftedFFTed)
    arrayInvertFFTed = np.fft.ifft2(arrayRevertedShiftedFFTed).real
    arrayBufferUint8 = arrayInvertFFTed.astype(np.uint8)

FFTのパワースペクトラムは、こうすると(512,512)のuint8のグレースケールとして表示可能なndarrayに変換できる。
ここでは最小を0に、最大を255に正規化しているが、クリップするだけでもいける。

    arrayBuffer = np.copy(arrayPowerSpectrumFFTRealPart)
    min = np.min(arrayBuffer)
    arrayBuffer[:,:] -= min
    max = np.max(arrayBuffer)
    arrayBuffer[:,:] /= max
    arrayBuffer[:,:] *= 255
    arrayBufferUint8 = arrayBuffer.astype(np.uint8)

f:id:nobu_macsuzuki:20210101024843p:plain

ここからがOpenCVの真骨頂、顔と目の認識。

    pathCascadeEye = os.path.join(pathCascade, 'haarcascade_eye.xml')
    pathCascadeFrontFace = os.path.join(pathCascade,'haarcascade_frontalface_default.xml')

    cascadeClassifierFace = cv2.CascadeClassifier(pathCascadeFrontFace) # it instatiates cascade classifier object
    cascadeClassifierEye = cv2.CascadeClassifier(pathCascadeEye)

    arrayFaces = cascadeClassifierFace.detectMultiScale(arrayImageGray) # it returns the list of ([tartX, startY, width, height]

    colorFace = (255, 0, 0) # color is tuple in CV
    colorEye = (0, 255, 0)
    LineThickness = 2

    for (facePositionX, facePositionY, faceWidth, faceHeight) in arrayFaces:
        cv2.rectangle(arrayImageColor, (facePositionX, facePositionY), (facePositionX + faceWidth, facePositionY + faceHeight), colorFace, LineThickness)
        arrayBufferFace = arrayImageColor[facePositionY: facePositionY + faceHeight, facePositionX: facePositionX + faceWidth]
        arrayBufferFaceGray = arrayImageGray[facePositionY: facePositionY + faceHeight, facePositionX: facePositionX + faceWidth]
        arrayEyes = cascadeClassifierEye.detectMultiScale(arrayBufferFaceGray)
        print(arrayEyes)
        for (eyePositionX, eyePositionY, eyeWidth, eyeHeight) in arrayEyes:
            cv2.rectangle(arrayBufferFace, (eyePositionX, eyePositionY), (eyePositionX + eyeWidth, eyePositionY + eyeHeight), colorEye, LineThickness)

f:id:nobu_macsuzuki:20210101025059p:plain

お次はRGBチャネル別のヒストグラム

[f:id:nobu_macsuzuki:20210101025258p:plain]    colors = ('b','g','r') #matlibplot has the color index, https://matplotlib.org/3.1.0/api/colors_api.html#module-matplotlib.colors
    for (indexColor, color) in enumerate(colors): # it provides list of object with index, in this case [(0, 'b'), (1, 'g'), (2, 'r')] 
        arrayHistogram = cv2.calcHist([arrayImageColor],[indexColor],None,[256],[0,256]) # it retunrs list of channel histogram
        plt.plot(arrayHistogram, color = color)
        plt.xlim([0,256])

f:id:nobu_macsuzuki:20210101025325p:plain

自作したときには、FFTとかライブラリだけで200-300行はあったし、画像を開いてFFTに流せるようにバイト列に変換してうんぬんだって書かねばならない。
射影変換も同様で、affine変換(線形変換)は簡単なのだが、射影変換はひーこらひーこらいいながら書いた。
それが上記の通り、どれも数行 - 十数行で書けてしまうのがすごい、うーむ、恐ろしい生産性。

ちなみに画像の表示はこのようにする。

    cv2.imshow('Color image',arrayImageColor)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

一行目が表示、二行目が入力待ちで、キー入力があると3行目でウィンドウを閉じる。

github.com

Pillowで画像処理

Pillowは、もし誤解していなければ、GDIのような単純なグラフィクスライブラリーだ。
バッファーを準備して、線を書いたり、単純な図形を書いたり、画像を読みだして表示したりできる。
setpixelやgetpixelもあるので、それを利用して画像処理を書くこともできる。
こんな感じだ。

    imageFromFile = Image.open(filename) # it generates the PIL.Image.Image object
    imageFromFile.show()
    input()

    # manipulate R, G, B to set all to Y
    imageBuffer = Image.new('RGB', imageFromFile.size) # it generates the PIL.Image.Image object
    for y_pos in range(imageFromFile.size[1]):
        for x_pos in range(imageFromFile.size[0]):
            (inR, inG, inB) = imageFromFile.getpixel((x_pos, y_pos)) # the cordinate is type, it returns pixel value of tuple
            Y = int(inR * .3 +  inG * .6 + inB * .1)
            outR = Y
            outG = Y
            outB = Y
            imageBuffer.putpixel((x_pos, y_pos), (outR, outG, outB)) # the cordinate is type, the pixel value is tuple
    imageBuffer.show()

ここでは、RGB値を元に輝度(Y)値をITU-R.BT601を使って概算(R x 0.3 + G x 0.6 + B x 0.1、ビット誤差が気になる場合には R / 4 + G x 5 / 8 + B x 1 / 8 とするのが吉)で求めている、https://ja.wikipedia.org/wiki/YUV
同じ方法で画素ごとのフィルターや空間フィルターが書ける。
Pillowには描画機能がないので、showメソッドはOSデフォルトのビュワーを開く。


github.com