読者です 読者をやめる 読者になる 読者になる

cloverrose's blog

Python, Machine learning, Emacs, CI/CD, Webアプリなど

ゆゆ式 Advent Calendar 2016 17日目 AR縁ちゃんを作りました

ゆゆ式 Android OpenCV AR

f:id:cloverrose:20161218000239p:plain

ゆゆ式アドベントカレンダー2016の17日目です。

www.adventar.org

今年自分はARで縁ちゃんを発見するアプリ、「AR縁ちゃん」を作りました。

AR縁ちゃんとはカメラの画像をリアルタイムで解析して、縁ちゃんの目っぽいもの(コンセント)があったら、そこに縁ちゃんを描画するアプリです。

動画を見ていただくとわかりやすいかと思います。

youtu.be

実装中のコードはGithubにあげてあります。まだ改善したい箇所があるのでぼちぼち更新します。

github.com

コードが完成したら版権を考慮してなんらかの形で公開したいと思います。

昨日は

hayashida_2ndさんのゆずこたち三人と猫ちゃんのかわいいイラストでした。

偶然だけど僕の飼ってる猫と毛色が同じだった!

今年もかわいいイラストや面白いゲーム、考察、まとめなど0:00が楽しみな毎日でした。

自分も0:00に投稿したかったのですがギリギリになってしまいました m(_)m

かわいい縁ちゃんのコンセント目について

gifmagazine.net

コンセント縁ちゃんにはこんなかわいいイラストがあります。

ゆかりちゃんの目がコンセントっぽいところを集めた動画もあります。

www.nicovideo.jp

余談

Androidのカメラ画像の四角形を認識したい!と同期のエンジニアに相談したらOpenCVでできそうだよ、と下記のサイトを紹介してもらったのがスタートになります。

OpenCV shape detection - PyImageSearch

具体的にはコンセントを認識したいと話したら縁ちゃんの目だなと察した友達がいました。

以下は技術メモになります。OpenCVJavaで使うサンプルがあまりないので他の人の役に立つと幸いです。

続きを読む

Android OpenCV face-detection 動かすメモ

ブログを色々参考にしながら進めたが結構苦戦したのでまとめておく。

環境

手順

0. Android StudioOpenCV for Android を導入する

OpenCV for AndroidをAndroid Studioに導入するメモ - Qiita をなぞる

うまく動いた。

1. NDK使わないバージョンのOpenCV (15-puzzle) を試す

AndroidStudio2.0でOpenCV3.1(sample編) - プログラミング好きな脳の引き出しをなぞる

これはうまく動いた。

注意点

  • Android端末にOpenCV Managerをインストールしておくこと
  • build.gradleが複数ある
2. NDK使うバージョンのOpenCV (face-detection) を試す

AndroidStudio2.0でOpenCV3.1(sample with NDK編) - プログラミング好きな脳の引き出し をなぞる

一部漏れがあったので差分だけ補う。エラーが出たら以下の差分を試して解消を試みる。

差分1. NDKをインストールする

Android Studioを開いて、File>Project Structureで開いたWindowにDownload NDKみたいなのがある。

差分2. gradle.propetiesに以下を追加。

android.useDeprecatedNdk=true

差分3. Application.mkを編集

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a arm64-v8a
APP_PLATFORM := android-8

arm64-v8aを追加した

参考:http://sarl-tokyo.com/wordpress/?p=553

差分4. ライブラリをコピーする

cp -r ~/Downloads/OpenCV-android-sdk/sdk/native/libs ~/Desktop/face-detection/openCVLibrary310/src/main/jniLibs

参考:java - android Static Initialization opencv 3.0 Cannot load library "opencv_java3" - Stack Overflow

まとめ

OpenCV for Android 3.x系じゃ動かなかったから2.4.11を試したという記事もあるが3.1でちゃんと動くことが確認できた

Gist的なメモ

openCVLibrary310/build.gradle

apply plugin: 'com.android.library'

android {
    compileSdkVersion 21
    buildToolsVersion "25.0.0"

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 21
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

openCVSamplefacedetection/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "25.0.0"

    defaultConfig {
        applicationId "org.opencv.samples.facedetect"
        minSdkVersion 21
        targetSdkVersion 21

        ndk {
            moduleName "detection_based_tracker"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    sourceSets.main {
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
        jniLibs.srcDir 'src/main/libs' // This is not necessary unless you have precompiled libraries in your project.
    }

    task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
        def ndkDir = "/Users/rose/Library/Android/sdk/ndk-bundle"
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                '-j', Runtime.runtime.availableProcessors(),
                'all',
                'NDK_DEBUG=1'
    }

    task cleanNative(type: Exec, description: 'Clean JNI object files') {
        def ndkDir = "/Users/rose/Library/Android/sdk/ndk-bundle"
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                'clean'
    }

    clean.dependsOn 'cleanNative'

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn buildNative
    }
}

dependencies {
    compile project(':openCVLibrary310')
}

build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

gradle.propeties

android.useDeprecatedNdk=true

local.properties

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Sun Dec 04 18:10:43 JST 2016
ndk.dir=/Users/rose/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/rose/Library/Android/sdk

settings.gradle

include ':openCVLibrary310'
include ':openCVSamplefacedetection'

Ubuntu ServerでX serverを動かす手順メモ

AWSのEC2のUbuntu Server (Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-a21529cc) でX serverを動かす手順メモ

sudo apt-get update && sudo apt-get upgrade && sudo reboot
sudo apt-get install xorg
sudo apt-get install python-qt4
sudo xinit &
export DISPLAY=:0.0

xinit動かすと error setting MTRR (base = 0xf0000000, size = 0x00100000, type = 1) Invalid argument (22) のエラー出るけどPyQt4はちゃんと動作してた。今のところ問題なさそう。

PyQt4でJavaScriptでレンダリングしてるWebページをScraping! CookieとUserAgentも扱うよ

JavaScriptレンダリングしてるWebページをScrapingしたい!!!

自分の環境

  • Virtualbox (Host OS Windows 7 64bit)
  • Guest OS Ubuntu16.04.1 LTS Desktop 64bit
  • Python 2.7.12
  • PyQt4は sudo apt-get install python-qt4 でインストール

実現方法

調べたら素晴らしいBlogが見つかった。

Ultimate guide for scraping JavaScript rendered web pages | IMPYTHONIST

BlogではPyQt4のWebKitを使ってJavaScript部分もレンダリングしたHTMLを取得する方法を紹介している。

(HTMLのParseに関する話もあるが、HTMLさえ取得できればBeautifulSoup使えばいい)

このBlogはコメント欄も有用な情報がたくさんあって、同じことをする別のライブラリやプロジェクトを示唆している人もいた。

ただやりたいことに対して複雑なライブラリを使いたくなかったので、このBlogの方法で実現してみた。

Blogの通りに動かすとちゃんと http://pycoders.com/archive/スクレイピングできた!!

タスク

だいたいやりたいことの大枠はBlogのソースコードで実現できた。

あとは細かい部分を詰めていくだけ。やりたいところはこの2つ

  • Cookieを扱いたい
  • UserAgentを扱いたい

Cookieを扱いたい

Blogのコメント欄にもあるけどQNetworkCookieJarを使えばいける

実現方法の詳細はこのSOがとても参考になった。

python - Permanent cookies with QWebKit -- where to get the QNetworkAccessManager? - Stack Overflow

手順

  1. EditThisCookie などを使ってCookie情報をJSONファイルに保存しておく
  2. QNetworkCookieを作る
  3. QNetworkCookieのListを作る
  4. QNetworkCookieJarを作る
  5. QWebPageにQNetworkCookieJarをセット .networkAccessManager().setCookieJar(cookiejar)

関連クラスの仕様

UserAgentを扱いたい

このSOが参考になった。

python - Setting useragent in QWebView - Stack Overflow

自分のUserAgentはここでわかるっぽい

手順

  1. What's My User Agent? - User Agent & Browser Tools などでCookieを調べる
  2. .userAgentForUrl に このCookieを返す関数をセットする

最終結果

# -*- coding:utf-8 -*-
import datetime
import json
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from PyQt4.QtNetwork import *


# 偽装したいUserAgent
USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
COOKIE_FILE = "cookie.json"


def customuseragent(url):
    u"""
    どのurlでも同じUserAgentを返す
    """
    return USER_AGENT


def make_cookiejar(filename):
    u"""
    JSON形式でExportされたCookieを読み込みQNetworkCookieJarを作る
    """
    with open(filename) as f:
        data = json.load(f)

    cookies = []
    for item in data:
        cookie = QNetworkCookie(item['name'], item['value'])
        if 'domain' in item:
            cookie.setDomain(item['domain'])
        if 'expirationDate' in item:
            cookie.setExpirationDate(datetime.datetime.fromtimestamp(item['expirationDate']))
        if 'hostOnly' in item:
            cookie.setHttpOnly(item['hostOnly'])
        if 'path' in item:
            cookie.setPath(item['path'])
        if 'secure' in item:
            cookie.setSecure(item['secure'])
        cookies.append(cookie)

    cookiejar = QNetworkCookieJar()
    cookiejar.setAllCookies(cookies)
    return cookiejar


#Take this class for granted.Just use result of rendering.
class Render(QWebPage):
    def __init__(self, url):
        self.app = QApplication(sys.argv)
        QWebPage.__init__(self)
        self.loadFinished.connect(self._loadFinished)

        self.userAgentForUrl = customuseragent
        cookiejar = make_cookiejar(COOKIE_FILE)
        self.networkAccessManager().setCookieJar(cookiejar)

        self.mainFrame().load(QUrl(url))
        self.app.exec_()

    def _loadFinished(self, result):
        self.frame = self.mainFrame()
        self.app.quit()


def main(url, filename):
    r = Render(url)
    result = r.frame.toHtml().toUtf8()
    with open(filename, 'w') as f:
        f.write(result)


if __name__ == '__main__':
    URL = 'http://pycoders.com/archive/'
    FILENAME = 'result.html'
    main(URL, FILENAME)

補足

Qtの独自の型があるけど

  • QByteArrayってなってるところはPythonのstr型でOK
  • QDateTimeってなってるところはPythonのDatetime型でOK
  • frame.toHtml()の結果をtoUtf8()してあげないと日本語が文字化けで?になってしまう

感想

3年前くらいにMacPyQt動かそうとしてメッチャ苦労したけどUbuntuでは超簡単に動いた。

わりとすぐにいいBlogにたどり着くことができてよかった。

PyQtのリファレンス体裁が汚すぎる…

Raspberry Pi3 起動に苦戦

昨日Raspberry Pi3 ModelBが届いた

www.amazon.co.jp

NOBESのダウンロードを30分位待って、よっしゃ遊ぶぞって思って、電源を挿したらPWRの赤色LEDは点滅するけど、モニターには何も映らない。

ぐぐったら、Raspberry Pi3からは電源として5V, 2.5Aのものを使う必要があることを知った。

あわててAmazonで注文した。 www.amazon.co.jp

次の日、アダプターが届いたので、よっしゃ遊ぶぞって思って、電源を挿したら(以下略)

www.raspberrypi.org によると

The Raspberry Pi's bootloader, built into the GPU and non-updateable, only has support for reading from FAT filesystems (both FAT16 and FAT32), and is unable to boot from an exFAT filesystem. So if you want to use NOOBS on a card that is 64GB or larger, you need to reformat it as FAT32 first before copying the NOOBS files to it.

ということで僕のSDXC64GB exFATにフォーマットされてるから読み込めないっぽい。ひとっ走りSDカード買いに行かないと。

pywebhdfsにHAとFederationをサポートするPRがマージされた

WebHDFS Hadoop python

WebHDFSについて

WebHDFShdfsコマンドではなくREST APIにhttpでアクセスできる便利なもの。

Hoop(httpfs)とwebhdfsの違い - たごもりすメモとかが図もあってわかりやすいと思う。

背景1 (hdfsコマンドへの不満)

MapReduceなどを使って解析を行って、その結果を可視化するとかを行っていると、スクリプトの中で頻繁にHDFSにアクセスすることになった。

そしてShellスクリプトが量産されるわけだが、自分はPythonスクリプトを書きたかった。

最初はsubprocessの中でhdfsコマンドを叩いていたが、レイテンシが結構あるし、lsコマンドの結果をパースしてファイル名を取得する必要があって微妙だった。

そんな話をしたらWebHDFSがあるよと教えてもらった。

背景2 (WebHDFSとの出会い)

WebHDFSに触ってみたら、レイテンシがすごく短いし、JSONが返ってくるので扱いやすい。心が踊った。

テンションが上ってHDFSのWebUIみたいなことができるシェルインタプリタを作った。 できる処理はls, cat, cd, pwdでread/get的なものだけにしていた。

その時自分でWebHDFSのREST APIのうすいラッパーを実装した。

これがわりと必要十分な機能は揃っていたためチームでもこのapi.pyが使われるようになった。

背景3 (自作ラッパーの限界)

自作ラッパーはシェルインタプリタ用に遊びで作っていたので、実業務で利用されるとカバーしているAPIが少なかった。

特に新規ファイル作成とかは、1回NameNodeにリクエストを投げて、返ってきたDataNodeのURIに対して再度ファイル内容とともにリクエストを投げるという形式で、自分で全部実装するのはめんどくさすぎた。

また認証周りもめんどくさくて、APIに渡す引数が増えていきそうだった。

そこでOSSPythonのWebHDFSラッパを探すことにした。

ライブラリ探し

PythonからHDFSを操作する - 偏った言語信者の垂れ流しに辿り着いた。

そこでは2つのライブラリが比較されていた。2013年の比較だが参考になった。

実際、PYPIを見てもWebHDFSライブラリはリリースが2014-01-20と古く、もうメンテナンスされていない気がした。

そこでpywebhdfsを使うことにした。しかしこのライブラリではHAとFederationがサポートされていなかった。

HAとFederationについて

HA(High Availability)とはHadoopのNameNodeが単一障害点だった欠点を解消するために、ActiveとStandbyという2つ以上のNameNodeを起動しておき、ActiveなNameNodeが落ちたらStandbyだったNameNodeが自動でActiveに切り替わるという仕組みで、実際の業務でHadoopを使うなら必須な機能。

Federationは複数のNameNodeがメタ情報(ディレクトリ構造とか)を分担してメモリに保持する仕組み。貧弱なメモリのNameNodeで大規模なクラスタを管理しようとすると全てのメタ情報が載り切らない。この時/data/以下はNameNode1で、/user/以下はNameNode2でそれ以外はNameNode3でという感じで分割できる仕組み。

どちらも自分たちのプロジェクトでは使っている。

現状pywebhdfsは1つのNameNodeのホスト名を渡すので、Activeが落ちた時に、APIはずっとエラーを返すようになってしまい、それを外側で検知して切り替えたりしないといけない。

HAとFederationのサポートの実装

いろいろ考えたが、pywebhdfsのIssueにもHAのサポートが欲しいという声が上がっていたので、PRを投げることにした。

しばらくの土日はいろんなパターンでHA/Federationをサポートする仕組みを実装した。

最終的には、パスにマッチする正規表現とそれに該当するNameNodeのリスト(HAならActiveとStandby)を順序付きの辞書でAPIコンストラクタに渡すことにした。

順序付きの辞書にしたのは、それ以外のパス(.*)とは最後にマッチさせたいからだ。

実装にあたって気をつけたのは、HA/Federationを利用していないユーザーには今までと同じインターフェースを保つこと。

そしてPRを投げた。

support federation and HA by cloverrose · Pull Request #22 · pywebhdfs/pywebhdfs · GitHub

もらったレビューを反映して、今朝マージされた :)

1ヶ月待っていたので感慨深かった。

f:id:cloverrose:20150718113204p:plain

余談

HDFSのシェルインタプリタは自作のapi.pyではなく、自分のパッチが当たったpywebhdfsを使って実装し直して現在も気に入って使っています。

Pythonインタプリタを作るときにはreadlineをラップしたcmdという便利なものがあります。

TABで補完とかが簡単に実装できるし、入力読み取り→実行のループも勝手にやってくれます。初めて知ったけど、今後また何か作るときに使っていきたい。

ピープルウェア 第3版 感想

感想が、読書メーターに収まらなかったのでブログに書きます。

人月の神話の中でおすすめされていたので読みました。

ピープルウエア 第3版

Amazon.co.jp: ピープルウエア 第3版: トム・デマルコ, ティモシー・リスター, 松原友夫, 山浦恒央: 本

新しい知見が得られたり、面白かった章。特にためになったのが4章と34章(☆付けた)

1章 今日もどこかでトラブルが

ハイテクの幻影

IT企業で働いてるとついつい、全て自動化できるし、テクノロジーで解決できると思ってしまうけど、製品は置いておいて、確かに実際働いてる場所では人間臭いこと、社会学的なことがいっぱいだと再認識した。

☆4章 品位質第一・・・時間さえ許せば

自分が品質に拘るタイプなので、「プログラムの品質は開発者の自尊心と強く結びついてる」というのに非常に共感できた。チームの人の自尊心についても考えていけるようになろう。

13章 オフィス環境進化論

この本のオフィス環境の部分は、自分でどうこう出来る部分が少ないのであまり実りは無かったけど、この章で出てきた参考図書「時を超えた建築の道」が面白そうだなと思った。

あと、イヤホンで音楽聞いてると、音楽は右脳で聴いているので、作業をする左脳と競合しないからエラー発生率や開発速度はそこまで落ちないけど、右脳を使うひらめきが起きにくくなるというのが目からうろこだった。

22章 ブラックチームの伝説

読み物として面白い。プログラムのテストチームの話。

24章 続、チーム殺し:残業の予期しない副作用

残業すると、自分の人生の大事な時間が削られる。チームで長期間の残業を強制すると、育児などで残業をしない人への不満が募り、うまくいっていたチームも崩壊するという話。会社との雇用契約で結んだ時間ないしか個人を束縛してはいけないなと感じた。

26章 スパゲッティディナーの効果

読み物として面白い。本能的に優れたマネージャーの話。

27章 裃を脱ぐ

チームメンバーに無理にでも出張を組んで、会社から出させてあげる。

☆34章 変化を可能にする

ちょうど自分の今の体験と重なって、とても勉強になった。

  • 人は誰しも本質的に変化を嫌う
  • 変化に対する反応は以下のように分類され、自分が変化を起こそうとするときに、1.と3.は敵、真の味方は2.であるという話
  1. むやにみ忠実(全く質問もしない)
  2. 信じているが室もなり
    1. 懐疑論者(「証拠を見せろ」)
    2. 受身の傍観者 (「私に何か関係有ることがあるの?」)
    3. 反対者(変化への恐れ)
    4. 反対者(権力を失うことへの恐れ)
  3. 戦闘的な反対者(妨害したり破壊してやろう)
  • 変化を受け入れてもらうには論理的な意見を主張するのではなく、情緒的に、変化前の人たちに敬意を持って
  • 変化のモデルは古い状況-(他からの要素)->混乱-(アイデアの変形)->実践と統合->新しい状況という4つのフェーズがある

自分の体験というのは、CI・CDツールとしてChef(Ruby)に切り替えていくのが、Fabric(Python)の知識がある状態から、ChefとRubyという初心者に変わってしまうこととか、Chef導入に素直に賛成だった人より、懐疑論者だった自分(ChefとFabricの比較、Chefに移行して得られるメリットと学習コストのデメリットなどをまとめた)の方が、Chef以降に移ってからは、Kitchenのテストツールなどの調査など、真の味方なきがするなという部分。


自分はプログラミングも好きだけど、こういうソフトウェア開発における問題とかにも興味あるので非常に面白かった。