2015年3月22日日曜日

ラズパイでカメラを使う方法

ラズパイでカメラモジュールを使ってみた
使用したのは「Raspberry Pi カメラモジュール
ラズパイ用のカメラモジュールのため、かなり簡単に作れた
まぁ全然カメラ起動しなくて苦戦したけどw

 カメラの取り付け方
下画像の赤い枠に注目
奥で観にくいけど、白い部分を持ち上げて、カメラモジュールのピラピラを差し込む。
カメラのピラピラは金の線が入ってる方を手前にする必要があるので注意

 カメラモジュールの設定
ラズパイの電源を入れてカメラの設定していく。普段SSHで作業してる人はHDMI端子を用意する必要がある。ラズパイの設定画面を操作するので画面出力するから
  • 設定画面を開く
  • カメラモジュール有効可
  • 下の画像みたいなの出るから、「Enable Camera」を選択。「Enable」を選らんで「Enterキー」押す
    下の画像に戻るので、「Escキー」を押す。←これ気づかずに「Finish」押して再起動してたらカメラ有効にならなくてめっちゃ迷ったww
 写真の撮り方と確認方法
撮り方はこのコマンド ※「photo.jpg」は保存する写真の名前なので任意

画像を見る方法はコレ ※「photo.jpg」は表示したい画像の名前なので任意


できた!!!

Canvasクラスを制すものは描画を制す

AndroidはViewを描写をするときonDraw(Canvas canvas)メソッドで行っている
この引数のCanvasクラスは描画するための最小単位のクラスで、こいつでテキストだしたり、画像出したりしてる。android1.0から存在する大先輩クラス
今ではほとんどCanvasを意識せずViewの組み合わせによりUI構築を行うが、結局は各ViewのonDraw()で描写してるだけに過ぎない。ゆえにImageView、TextView、ListViewなどのViewを継承してるクラスのonDraw()をオーバーライドさせてやれば何でも描画できる!
てことでonDraw時のCanvasを制御する方法をまとめる
 文字の逆さ表示(TextViewのOverride)
やり方はCanvasを回転させて逆さにする。
Canvas# rotate()メソッドに回転方向、回転軸x、yを引数にして回転させる
回転軸は設定しないと、Viewの左上が回転軸になる。Viewの中心で回転させるので、横幅、縦幅の半分が回転軸
/** 逆向き表示TextView */
TextView textView1 = new TextView(this) {
    @Override
    public void onDraw(Canvas canvas) {
        canvas.save();
        canvas.rotate(180, getWidth() / 2, getHeight() / 2);
        super.onDraw(canvas);
        canvas.restore();
    }
};
 文字の反転(TextViewのOverride)
やり方はCanvasの倍率を変更するメソッドで倍率をx軸を-1倍にする。
そうすると、X軸に対し、線対称に表示される
/** 反転表示TextView */
TextView textView4 = new TextView(this) {
    @Override
    public void onDraw(Canvas canvas) {
        canvas.save();
        canvas.scale(-1, 1);
        canvas.translate(-getWidth(), 0);
        super.onDraw(canvas);
        canvas.restore();
    }
};
 影付き文字(TextViewのOverride)
影付けるにはCanvasの文字を描写するメソッドを使い、少しずれた位置に表示文字色より、薄い色で文字を描く
/** 影付きTextView */
TextView textView2 = new TextView(this) {
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
    @Override
    public void onDraw(Canvas canvas) {
        canvas.save();
        // 影の部分を先に描いてその上にほんとの文字を描く
        // 色の設定。本物の色を取ってきて、RGBに加算して色を足す
        p.setColor(getTextColors().getDefaultColor() | 0x00AFAFAF);
        // 文字のサイズを合わせる
        p.setTextSize(getTextSize());
        // 表示文字を合わせる
        String text = getText().toString();
        // 文字の表示位置調整。文字の高さ、マージンとか計算
        FontMetrics fm = p.getFontMetrics();
        float size = (fm.descent - fm.ascent);
        float margin = (getHeight() - size) / 2;
        canvas.drawText(text, 0, margin + getTextSize(), p);
        // 表示位置のずれ具合調整。だいたい5とか10くらい
        int dx = 5;
        canvas.drawText(text, 0 + dx, margin + getTextSize() + dx, p);
        canvas.restore();
        // 通常描写
        super.onDraw(canvas);
    }
};
 スクロールアニメーション(TextViewのOverride)
Handler使って描画時間を調整しつつ、Canvasを平行移動させていくことで文字をずらしていく。
Handlerでスレッド止めたりするのキューたまりすぎないか心配なので、Threadとかで時間管理した方がいい気がする
/** スクロールアニメーションTextView. */
TextView textView3 = new TextView(this) {
    Point point = new Point();
    Handler handler = new Handler();
    Runnable runnable = new Runnable() { public void run() {
            // 10m秒このスレッドの時間を止める。その後強制描写メソッドで、onDraw()を呼ぶ
            try {Thread.sleep(10);} catch (InterruptedException e) {}
            invalidate();}
    };
    @Override
    public void onDraw(Canvas canvas) {
        canvas.save();
        // 結構ややこしい計算だけど、幅の2倍移動するので、移動式がこんな感じ
        canvas.translate(point.x + getWidth(), 0);
        super.onDraw(canvas);
        canvas.restore();
        // 座標を3ずらして表示。表示幅の2倍より大きくなったらリセット
        point.x = (point.x -3) % (getWidth() * 2);
        handler.post(runnable);
    }
};
 角度を変えた画像の重ね合わせ(ImageViewのOverride)
この画像を回転させながら重ね合わせて、扇風機の羽を作る
文字反転と同じようにCanvasをView中心で回転させて、onDraw()を何度も呼ぶ。
/** 重ね合わせImageView. */
ImageView imageView = new ImageView(this) {
    @Override
    public void onDraw(Canvas canvas) {
        canvas.save();
        // 回転軸の決定
        PointF axis = new PointF(getWidth() / 2, getHeight() / 2);
        super.onDraw(canvas);
        canvas.rotate(90, axis.x, axis.y);
        super.onDraw(canvas);
        canvas.rotate(180, axis.x, axis.y);
        super.onDraw(canvas);
        canvas.rotate(270, axis.x, axis.y);
        super.onDraw(canvas);
        canvas.restore();
    }
};
こんな感じで簡単にViewの編集が行える

2015年3月10日火曜日

ListViewをカスタマイズする方法

アニメ番組表 API使った通信Androidアプリを作ってみた」でアニメのタイトル一覧をListViewで表示した。
でもタイトルだけ表示しても面白くないので、ListViewをカスタマイズする

ListViewを制する者は、AndroidのView表示を制す!
というだけあっていろいろなテクニックが必要だった

 ListViewの作り

ListViewに文字を表示させるには
表示させたいデータをAdapterクラスに入れて、Apapterに設定してあるレイアウトを基にViewを組立て、ListViewに表示させる
既存のAdapterでリストをつくるとこうなる
// Viewの生成に必要
Context context = this.getApplicationContext();

// Adapterの設定 Adapterのコンストラクタ(Context,LayoutのID,リストID, リストデータ)
String[] list = { "りんご", "ゴリラ", "ラッパ", "パイナップル" };
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_expandable_list_item_1, list);

// AdapterをListViewにセット
ListView listView = new ListView(context);
listView.setAdapter(adapter);
「android.R.layout.simple_expandable_list_item_1」は既存のセル用レイアウトでTextViewが1個あるだけ
ArrayAdapterのコードを見てみると、レイアウト内のTextViewに文字列を入れる仕組みになってた
オリジナルのListViewを作るにはAdapterのレイアウトを組み立てる仕組みを作ってやればいい

 Layoutの定義

表示するセルのレイアウトを作ってく
プロジェクトフォルダ/res/layout/


定義は右や上のフォルダの位置にxmlファイルつくって描く
ソースコード上でも描けるけど、この方が見やすいからねw
名前は何でもいいので、「list_aitem.xml」としておく。この名前はレイアウトのIDとして後々使用するので適当すぎると困る
今回は曜日、開始時間、放送局、タイトルをTextViewで表示する
こんな感じで設定

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/week_view"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_gravity="center"
        android:layout_margin="10dip"
        android:textColor="#000000"
        android:textSize="50sp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dip"
            android:background="#0FFFF0"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/time_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#000000"
                android:textSize="20sp" />

            <TextView
                android:id="@+id/tv_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dip"
                android:textColor="#000000"
                android:textSize="20sp" />
        </LinearLayout>

        <TextView
            android:id="@+id/title_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:singleLine="true"
            android:ellipsize="end"
            android:textColor="#000000"
            android:textSize="30sp" />
    </LinearLayout>

</LinearLayout>
アニメタイトルのTextViewが問題
TextViewは列をはみ出そうとすると自動で改行を入れてくれる。しかし、ListViewでこれをやられると各セルの高さがばらばらになる
個人的にセルの高さは統一したいので、TextViewは一行で表示、はみだした分を…となるように設定する
色つけてる箇所がポイントで、
singLineを"true"で一行表記設定
ellipsize="end"で…の位置を設定
ちなみにellipsize属性に設定できる他のフラグはこんな感じ
フラグ効果
end文末が…アニメタイ…
start文頭が……メタイトル
middle文の途中が…アニメ…トル
none…をしないアニメタイト

 Adapter作り

上記コードでは既存のAdapterを使っているが、今回は自作したレイアウトにデータを詰め込む実装がいるのでBaseAdapterを継承してオリジナルAdapterを作っていく
データクラスとしてAnime.javaを作っとく。
とりあえずAnimeAPIから得られる情報一式をメンバ変数にする
class Anime {
    public String title, url, time, station, state, next, episode, cable, today, week;
}
BaseAdapterは抽象クラスで実装しないといけないクラスがいくつかある
この辺はだいたいテンプレ。特殊なことしないならこれでOK
/**
 * アニメ表示用Adapter.
 *
 */
public class AnimeAdapter extends BaseAdapter {
    private static final int LAYOUT_ID = R.layout.list_aitem; // 上記で定義したレイアウトのID
    private Anime[] list;     // データリスト
    private Context mContext; // レイアウトIDからLayoutインスタンス取得に必要
    
    /** コンストラクタ. */
    public AnimeAdapter(final Context context, Anime[] animes) {
        this.mContext = context;
        this.list = animes;
    }
    
    @Override
    /** リストの数を取得 */
    public int getCount() {
        return list.length;
    }
    
    @Override
    /** リストから特定のAnimeを取得.戻り値Anime型にしとくのがミソ */
    public Anime getItem(int position) {
        return list[position];
    }
    
    @Override
    /** リストのID取得(まぁリストの順番でOK) */
    public long getItemId(int position) {
        return position;
    }

    @Override
    /** レイアウトにデータを設定する(一番したい処理) */
    public View getView(int position, View convertView, ViewGroup parent) {
        //TODO 色々テクニックいるので下で書く
        return null;
    }
}
次に一番やりたかった処理。レイアウトにデータを詰めて表示する処理
  1. セル用レイアウトにコンバート
  2. セル用レイアウトにデータを突っ込む処理
    ListViewは生成されたとき全てのセルを作ってるわけではない。セルのViewは画面に出たときに生成されてる
    そして、スクロールして新しいセルが表示されたとき、このメソッド(# getView())は呼ばれViewを生成する
    引数は
    ・position表示すべきリストのポジション
    ・convertViewListViewで表示するセル用レイアウトのインスタンス
    ・parentよくわからん(今回使わんので無視)
    最初convertViewはnullになっていて、実装上で設定する必要がある
    そこで必要なのがレイアウトのインスタンスを取得するLayoutInflaterクラス。システムからLayoutInflaterのインスタンスを呼び出し、# inflateメソッドでレイアウトを指定してレイアウトのViewのインスタンスを取得する
    LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    convertView = inflater.inflate(R.layout.list_aitem, null);
    
    そのレイアウトから各Viewのインスタンスを取得するには#findViewByIdメソッドをつかう
    XMLで定義したIDを指定してViewのインスタンスを得て設定する
    TextView textView = (TextView) convertView.findViewById(R.id.title_view);
    
    以上を行い、レイアウトを設定して戻り値に修正後のレイアウトを指定すればOK
    @Override
    /** レイアウトにデータを設定する(一番したい処理) */
    public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) this.mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.list_aitem, null);
        }
        Anime anime = getItem(position);
        ((TextView) convertView.findViewById(R.id.title_view)).setText(anime.title);
        ((TextView) convertView.findViewById(R.id.time_view)).setText(anime.time);
        ((TextView) convertView.findViewById(R.id.week_view)).setText(anime.week.substring(0, 1));
        ((TextView) convertView.findViewById(R.id.tv_view)).setText(anime.station);
        return convertView;
    }
    
  3. 高速処理
  4. 上記の方法でも処理できるが、毎回findViewById()をするとViewの呼び出し時間がかかってしまう難点がある
    それを回避するためにViewHolderという考え方を使う。レイアウトを使いまわす手法
    セル内のそれぞれのViewの参照値あらかじめ持たせておき、そこのインスタンスにデータを突っ込む
    @Override
    /** レイアウトにデータを設定する(一番したい処理) */
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView; // レイアウト
        ViewHolder holder;    // ホルダー
        if (v == null) {
            LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(R.layout.list_aitem, null);
            
            // ホルダーを設定する
            holder = new ViewHolder();
            holder.titleView = (TextView)v.findViewById(R.id.title_view);
            holder.timeView = (TextView)v.findViewById(R.id.time_view);
            holder.weekView = (TextView)v.findViewById(R.id.week_view);
            holder.tvView = (TextView)v.findViewById(R.id.tv_view);
            v.setTag(holder);
        }else{
         // レイアウトからViewフォルダーを取得
            holder = (ViewHolder)v.getTag();
        }
        Anime anime = getItem(position);
        // Viewフォルダに情報詰め込む
        holder.titleView.setText(anime.title);
        holder.timeView.setText(anime.time);
        holder.weekView.setText(anime.week.substring(0,1));
        holder.weekView.setBackgroundColor(color.get(anime.week));
        holder.tvView.setText(anime.station);
        
        return v;
    }
    // ホルダークラス
    class ViewHolder {
        TextView titleView, timeView, weekView, tvView;
    }
    
    これでOK
完成!

2015年3月2日月曜日

ラズパイで液晶ディスプレイに時間を表示させてみた(作成中)

I2C通信の勉強でラズパイに液晶ディスプレイに時間を表示させてみた。
めっちゃ疲れたーーーーwwww
配線間違えて電源入いらんようになったり、パーツ足らずに代用ためしたり、配線の理屈わからなかったり。。。 電子工作険しいww
とりあえず、まとめる

I2C通信とは

I2C通信は周辺機器とシリアル通信する仕組み
具体的にはラズパイがマスタ、周辺機器(温度計、加速度センサ、気圧)がスレーブとなって、データのやり取り(温度取得、加速度取得、気圧取得)ができるようになる
しかも、通信するための線が下の2本だけでできるってのも魅力
  • データのやり取りする線(SDA)
  • タイミング調整する線(SCL)

お買い物リスト

機材個数画像
16文字×2行LCDモジュール
ACM1602N1-FLW-FBW
今回の目玉。2行の文字を表示する
1個
ハンダごて
LCDモジュールをブレッドボードに接続する道具
1個
なまり線
ハンダでLCDの接着するためのもの。接着材みたいなの
5cm
ブレッドボード
ハンダ付けとかせず、
簡単に配線ができるやつ
1個
ジャンパー線(雄雌)
ラズパイのGPIOと
ブレッドボードをつなぐ線
5個
ジャンパー線(雄雄)
ブレッドボード上で配線を するための線
X個
可変抵抗(10kΩ)
抵抗値を変化させられるやつ。今回のクセモノ。てか、軽視しすぎてた
1個
抵抗(1kΩ)
電流を安定させる為に必要
1個
テスター
電流、電圧、抵抗とか調べれる。可変抵抗を調べるときに活躍する
1個

ラズパイでI2C通信をする為の準備

  • モジュールを使えるようにする
  • 読み込みできるように設定。管理者権限がいるので注意
    $ sudo vi /etc/modules
    i2c-dev # これを追記しとく
    
    デフォルトで読み込み拒否設定ファイルにi2cがいるので、コメントアウトしとく
    $ sudo vi /etc/modprobe.d/raspi-blacklist.conf
    # blacklist i2c-bcm2708  # 行頭に#つけてコメントアウト
    
  • モジュールのインストール
  • インストール
    $ sudo apt-get install i2c-tools
    
    モジュール読み込み。ここでI2Cの動作クロック周波数を50kHzにする
    普通は100kHzが標準。でも今回使う液晶が50kHzでないと作動しないのでコレで設定
    $ sudo modprobe i2c-dev
    $ sudo modprobe i2c-bcm2708 baudrate=50000
    
    次回起動時にモジュールが動くように設定
    上で、「i2c-dev」と「i2c-bcm2708」を読みこんだけど、「i2c-bcm2708」は電源入れ直したらまた読み込みし直しになるので、自動で起動するようにする
    $ sudo vi /etc/modprobe.d/i2c.conf # ←このパスに「i2c.conf 」ファイル作って、下記を書いとけばよい
    options i2c_bcm2708 baudrate=50000
    
  • Pythonで制御するライブラリをインストール
  • $ sudo apt-get install python-smbus
    
準備完了!

配線

作成中

実装

作成中

2015年3月1日日曜日

javaで画像処理する方法

画像処理は計算に時間かかるから普通はCとかでするんだけど、javaであえて書いてみる!
対象は「人によって青&黒または白&金に見える」って話題になったこのドレスの画像で

画像の仕組み

画像とは大量の点からできている
画像の情報を見るとき、下のかけ算をよくみると思うけどコレがこの画像を表示するための点の数

上記画像は、横方向に288個、縦方向に437個。つまり、125856個の点で構成されているってこと

この点を「ピクセル」といい、色の情報を持ってる
一般的にはピクセルは4Byteで表されてるんだけど、振り分けはこんな感じ
ビット00000000000000000000000000000000
意味
透過率
透過率はまぁいいとして、 それぞれ赤、青、緑を色深度を0x00〜0xFF(16進表記)で表してる
色深度は数値が低いほど暗い。例として、下記に示す※透過はFFとしておく
色の情報表示される色表示される色
0xFFFF0000赤(純粋)   
0xFF000100青(めっちゃ暗い)   
0xFFFF00FFマゼンダ(赤と緑を混ぜた色)   
こんな感じで、すべての色を赤青緑でつくってることになる

実装

javaには最初から画像処理するためのクラスがあるので、ダウンロードとかいらない。インポートさえすれば使える 一例に、画像を少し暗くする
/** ImageTest.java  画像を少し暗くする処理 */
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
 
public class ImageTest {
     
    final static String INPUT_IMAGE = "image.jpg";// 読み込み画像(このjavaファイルと同じフォルダに入れとく)
    final static String OUTPUT_IMAGE = "reslt.jpg";// 出力する画像(このjavaファイルと同じフォルダに出力される)
     
    public static void main(String[] args) throws IOException {
         
        // 画像読み込み
        File in_file = new File(INPUT_IMAGE);
        BufferedImage read = ImageIO.read(in_file);
         
        // 情報取得 w, hは縦、横のピクセル数。writeは画像の1ピクセルごとにアクセスできるようにしてる
        int w = read.getWidth(),h=read.getHeight();
        BufferedImage write =
        new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
         
        // 格ピクセルに処理していく
        for(int y=0; y < h; y++) {
            for(int x = 0; x < w; x++) {
                // 色抽出
                int color = read.getRGB(x, y); // 座標の色を取得
                int red   = (color & 0xff0000) >> 16; // 赤抽出
                int green = (color & 0x00ff00) >> 8;  // 緑抽出
                int blue  = (color & 0x0000ff) >> 0;  // 青抽出
                 
                // 色の情報変換
                red   = red / 2;   // 赤色の色深度を半分にする
                green = green / 2; // 緑色の色深度を半分にする
                blue  = blue / 2;  // 青色の色深度を半分にする
   
                // 出力用にフォーマット
                red  =(red << 16)  & 0xff0000;
                green=(green << 8) & 0x00ff00;
                blue =(blue << 0)  & 0x0000ff;
                color = 0xff000000 | red | green | blue;
                write.setRGB(x, y, color); // 情報変換した色をもとの座標に戻す
            }
        }
         
        // 画像の出力
        File out_file = new File(OUTPUT_IMAGE);
        ImageIO.write(write, "jpg", out_file);
    }
}
結果はこんな感じ
ポイントは色ついてるとこ(32~34行目)。それぞれの色を半分にすることで、暗くできる

応用

ポイント部分を変えることで、いろんなことができる
赤のみ抽出
// 色の情報変換
red   = red; // 赤はそのまま
green = 0;   // 緑は0にする
blue  = 0;   // 青は0にする
青のみ抽出
// 色の情報変換
red   = 0;   // 赤は0にする
green = 0;   // 緑は0にする
blue  = blue;// 青はそのまま
緑のみ抽出
// 色の情報変換
red   = 0;     // 赤は0にする
green = green; // 緑はそのまま
blue  = 0;     // 青は0にする
ビット反転
// 色の情報変換
red   = ~red;   // ビット反転
green = ~green; // ビット反転
blue  = ~blue;  // ビット反転


出力結果
赤抽出青抽出緑抽出ビット反転
ビット反転すると写真のネガみたいになる
不思議なことにビット反転すると白&金になるんだよねw
他にもモザイクをかけたり、色のヒストグラムとったりなどいろいろできるけど、今回はこれまでで