0

Flickrダウンローダを作った

Rubyで。ダウンロード部分はwgetに投げているのでUnix系でしか動かせないかもしれない。REXML::XPath使うのの勉強になった。

shokai / Flickr downloader / overview — bitbucket.org

タグ検索してユーザ指定したりもして(権限があれば)オリジナル画像を一括ダウンロードできる。


実行例

flickr-download beer,food 51753258@N00

で俺がbeerとfoodをタグを付けた写真が全部取ってこれて空腹を紛らわせれる。51753258@N00のところはユーザIDで、各ユーザぺージのRSSのURLを見ればわかる。


オリジナル画像を手に入れるためにFlickr APIのphotos.searchとphotos.getInfoのラッパーを実装した

0

PSoC – TX8モジュール

UARTモジュールとまるきり同じだけど、TXだけ使うとデジタルブロック1つで済むので節約したい時に使える。
とくにCY8C21334でCapSense (CSD)使うと、digital/analogともに残り1ブロックになるので、このTX8を使うかI2Cでもう1つ別のマイコンとやりとりしてそっちにシリアル通信してもらうかしないとパソコンとやりとりできない。


9600bps出すためのCPU設定。
内蔵24MHz → System Clock → VC1 → VC3と流れて分周していってTX8_1のクロックソースにする
TX8モジュール


Port_0_1まで結線する
TX8モジュール


main.c

#include <m8c.h>        // part specific constants and macros
#include "PSoCAPI.h"    // PSoC API definitions for all User Modules
void main()
{
  TX8_1_Start(TX8_1_PARITY_NONE);
  while(1){
    TX8_1_CPutString("this is test¥r¥n");
  }
}
これだけで8bit パリティ無し ストップビット1で送信できる。


あとは秋月のFT232RL基盤のRXDピンに流せばいい
橋本商会 秋月FT232RL基盤をPSoC CY8C29466で使ってみる


受信できた
TX8モジュール

3

C++/CLIで動かしているOpenCVのCvImageをC#.NETのBitmapオブジェクトにして読み込む

橋本商会: Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC# Formアプリから使う の続き。
FormアプリからOpenCVのHighGUIを別ウィンドウで開いていたが、Bitmapオブジェクトにキャストする事で.NETアプリのPictureBoxなどに読み込む事が出来る。
これにより画像処理だけ高速にC++で行い、アプリケーションのUIやネットワーク部分はC#.NETで実装するといういいとこどりが出来る。


■出来た物
FormにLoadImageボタンとPictureBoxを追加。
LoadButtonを押すとShadowCamera.dllから呼び出しているOpenCV側のCvImageが変換されてBitmapになって出てくる関数を作った。
C++/OpenCVのCvImageを.NETのBitmapに変換できた

今回作っているShadowCameraは背景を蓄積させて統計を取り、差分で人物の影だけを抜き出すライブラリなので、毎フレーム計算し続ける必要がある。runCapture()の最後でwhileループさせて実現している。
なので、1つBitmapオブジェクトを用意しておき、whileループの最後で処理結果を毎回保存しておいてそれをC#側から適当なタイミングで読みに行く実装にした。
DLLであるC++/OpenCV側のループを毎フレーム回し続けながら、それをブロックする事なくC# Formアプリ側から取得できる。


例によってOpenCV勉強用mercurialリポジトリchanges19としてcommitしてある。


なお、VisualStudioはC#はutf8なのにC++はshift_jisで保存されてしまい、utf8にするとビルドできなくなってしまうのでリポジトリビューアでは文字化けしてしまっている。悲しい。
文字化けしている部分には大したこと書いてないので無視していいし、必要ならリポジトリの左側からzipでダウンロードできる。


ちなみに今回の方法は、3年以上前の開発合宿の記事に匿名様が
コメントで教えてくれたSharperCVのCvImage型をSystem.Drawing.Bitmap型に変換する方法が書かれていてそれを元にした。
やはりできなかった事とかも書いてみるものだなと思った。もちろん助けをアテにしちゃいけないが


■ShadowCamera.dll側の変更
C++プロジェクトのShadowCameraに、System.Drawingへの参照を追加する。Bitmapクラスを使うため。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


「新しい参照の追加」ボタンを押す
C++/OpenCVのCvImageを.NETのBitmapに変換できた


.NETタブの下の方にスクロールしていって、System.Drawing を追加する。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


ShadowCamera.hにコードを書き加える

OpenCV Study: c9320e90f1be ShadowCamera/ShadowCamera.h
DLLへの参照を追加して

using namespace System::Drawing;


勝手に書き換えられたら嫌なので、privateでグローバル変数 shadow を作っておく
private:
    System::Drawing::Bitmap^ shadow;


DLL利用側のためのアクセサを用意する
public:
    System::Drawing::Bitmap^ ShadowImage(void){
        return this->shadow;
    }
これでDLL内のshadowというBitmapオブジェクトを、DLL外から読み出す事が出来るようになった。
いまいち ^ や ~ などをつけるルールを把握しきれていないけど、他のC++/CLIアプリがそうやっていたので先人の作法に倣うことにする。


あとはrunCapture()の最後のwhileループ内で、毎回作られるimgResultというIplImage(CvImage互換)オブジェクトをdynamic_castしてshadowに保存する。
this->shadow = dynamic_cast<System::Drawing::Bitmap^>
    (gcnew System::Drawing::Bitmap(w,h,w*3, 
    System::Drawing::Imaging::PixelFormat::Format24bppRgb,
    static_cast<System::IntPtr>(imgResult->imageDataOrigin)));
長いので変換部分のみ抜粋。ShadowCamera.hの85~125行目がwhileループです

dynamic_castやstatic_castの違いはC++の新しいキャストが参考になった。
オブジェクトの代入のためのcastは.NETの上で動いているからdynamic_castで、unmanaged領域と行き来するSystem::IntPtrと*char間のcastはstatic_castになるという理解でいいのかな?



■ShadowCamera.dllを使うC# Formアプリ側の変更
ボタン(buttonLoadImage)と画像を表示するためのPictureBox(pictureBoxResult)を配置。
PictureBoxはSizeModeをStretchImageにしておくと自動リサイズされて便利。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


で、ボタン押下イベントで
pictureBoxResult.Image = cam.ShadowImage();
BitmapをPictureBoxのImageプロパティに突っ込むだけで表示できる。楽ちんだ
C++/OpenCVのCvImageを.NETのBitmapに変換できた

終わり。

ボタンじゃなくてThreadなどで適当にsleepを入れながら読み込み続ければふつうに影カメラになる。

2

Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC# Formアプリから使う

重い画像処理をC++の画像処理ライブラリであるOpenCVでやって、ネットワーク処理は慣れ親しんだC#.NETでやりたい。
そこで、色々試行錯誤した結果、C#からは「処理し終わった画像を表示してくれるカメラ」に見えるような、OpenCVの処理部分を含んだDLLをC++/CLIで作ることにした。
(OpenCVそのものについてと開発環境構築は橋本商会 ? OpenCVをはじめたに書いた)


他にも連動のさせかたはSharperCVというC#ラッパーを使うとか、SocketでOpenCVで処理した結果の画像だけ送るとか色々とやり方はあるけど、
前者は2003年でプロジェクトが止まっていてcvPow等の配列関連の関数が無く、後者は俺のMicrosoft SDKのWinUser.hの6373行目あたりから文字化けしていてWinSocketが使えなかったりしたのでDLLを作るやり方に落ち着いた。


この方法のメリットは、まず納得のいくOpenCVの処理をC++単体のプロジェクトでいつもどおり書いて、それをほぼコピペすればDLLができてしまうところ。
ShaperCVを使う場合は全て関数を照らし合わせてC#で書き直さなければならないので面倒。
Socketを使う方はよく知らない。


DLLの作り方はかせいさんとこが参考になった。CLIを使う場合と使わない場合が解説されている。(今回は楽する為にCLIを使う)



■今回つくるもの
mercurialリポジトリをアップロードした。左のバーからzipでダウンロードするか、

hg clone http://shokai.org/projects/opencv-study/ opencv-study

してリポジトリを複製してください。

の2つを作ります。
影カメラライブラリの方は、OpenCV Study: 7537970cfc6b BgSubAvg/bgsubAvg.cppをDLL化したもので、cvRunningAvgを使って背景統計を取って背景差分して人型の影を作るもの → cvRunningAvgを使って背景統計を取り背景差分する – 橋本詳解


完成図はこれ。C#で作ったFormアプリで、ボタンを押すとDLLに書いてあるOpenCVの処理が起動してウィンドウが開き画像処理結果が表示/毎フレーム更新される。
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う



■C++でDLLを作る
まず空のソリューションを作る。
ソリューション名を右クリックして新しいプロジェクト追加
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う


VC++のクラスライブラリを作成する。名前はShadowCameraにした
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う


できたShadowCameraプロジェクトを右クリックしてプロパティを編集
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う


OpenCV用のライブラリを読み込む。
すべての構成->入力->追加の依存ファイル に

$(NoInherit) highgui.lib cxts.lib cv.lib cxcore.lib ml.lib cvaux.lib cvhaartraining.lib

を書く
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う

ShadowCamera.hのClass名をCameraに変更、さらにint runCapture()関数を書く。
この関数の中にOpenCVのmain()を全部突っ込んでしまえばそのまま動く。今回はbgsubAvg.cppのmain()をコピペしたけど他にも赤色重心追跡や肌色検出、単純なカメラ入力など色々作ったので好きなものをコピペしてよい。
OpenCV関連のヘッダを#includeで読み込むのを忘れずに。
(試しにint asdf()という単に数値を返すだけの関数も作っておいた。これがC#から呼べればちゃんとDLLとして読み込めているはず)

OpenCV Study: 7537970cfc6b ShadowCamera/ShadowCamera.h

// ShadowCamera.h
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <stdio.h>

#pragma once

using namespace System;

namespace ShadowCamera {

    public ref class Camera
    {
        // TODO: このクラスの、ユーザーのメソッドをここに追加してください。
    public:
        int asdf(void){
            return 15;
        }

    public:
        int runCapture(void){

            bool isStop = false;
            const int INIT_TIME = 50;
            const double BG_RATIO = 0.02; // 背景領域更新レート
            const double OBJ_RATIO = 0.005; // 物体領域更新レート
            const double Zeta = 10.0;
            IplImage *img = NULL;

            CvCapture *capture = NULL;
            capture = cvCreateCameraCapture(0);
            //capture = cvCaptureFromAVI("test.avi");
            if(capture == NULL){
                printf("capture device not found!!");
                return -1;
            }

            img = cvQueryFrame(capture);
            int w = img->width;
            int h = img->height;

            IplImage *imgAverage = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *imgSgm = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *imgTmp = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *img_lower = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *img_upper = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *imgSilhouette = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 1);
            IplImage *imgSilhouetteInv = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 1);
            IplImage *imgResult = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 3);

            printf("背景初期化中…¥n");
            cvSetZero(imgAverage);
            for(int i = 0; i < INIT_TIME; i++){
                img = cvQueryFrame(capture);
                cvAcc(img, imgAverage);
                printf("輝度平均 %d/%d¥n", i, INIT_TIME);
            }
            cvConvertScale(imgAverage, imgAverage, 1.0 / INIT_TIME);
            cvSetZero(imgSgm);
            for(int i = 0; i < INIT_TIME; i++){
                img = cvQueryFrame(capture);
                cvConvert(img, imgTmp);
                cvSub(imgTmp, imgAverage, imgTmp);
                cvPow(imgTmp, imgTmp, 2.0);
                cvConvertScale(imgTmp, imgTmp, 2.0);
                cvPow(imgTmp, imgTmp, 0.5);
                cvAcc(imgTmp, imgSgm);
                printf("輝度振幅 %d/%d¥n", i, INIT_TIME);
            }
            cvConvertScale(imgSgm, imgSgm, 1.0 / INIT_TIME);
            printf("背景初期化完了¥n");

            char winNameCapture[] = "Capture";
            char winNameSilhouette[] = "Silhouette";
            cvNamedWindow(winNameCapture, 0);
            cvNamedWindow(winNameSilhouette, 0);

            while(1){
                if(!isStop){
                    img = cvQueryFrame(capture);
                    if(img == NULL) break;
                    cvConvert(img, imgTmp);

                    // 輝度範囲
                    cvSub(imgAverage, imgSgm, img_lower);
                    cvSubS(img_lower, cvScalarAll(Zeta), img_lower);
                    cvAdd(imgAverage, imgSgm, img_upper);
                    cvAddS(img_upper, cvScalarAll(Zeta), img_upper);
                    cvInRange(imgTmp, img_lower, img_upper, imgSilhouette);

                    // 輝度振幅
                    cvSub(imgTmp, imgAverage, imgTmp);
                    cvPow(imgTmp, imgTmp, 2.0);
                    cvConvertScale(imgTmp, imgTmp, 2.0);
                    cvPow(imgTmp, imgTmp, 0.5);

                    // 背景領域を更新
                    cvRunningAvg(img, imgAverage, BG_RATIO, imgSilhouette);
                    cvRunningAvg(imgTmp, imgSgm, BG_RATIO, imgSilhouette);

                    // 物体領域を更新
                    cvNot(imgSilhouette, imgSilhouetteInv);
                    cvRunningAvg(imgTmp, imgSgm, OBJ_RATIO, imgSilhouetteInv);

                    cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮
                    cvDilate(imgSilhouette, imgSilhouette, NULL, 2); // 膨張
                    cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮

                    cvMerge(imgSilhouette, imgSilhouette, imgSilhouette, NULL, imgResult);
                    cvShowImage(winNameCapture, img);
                    cvShowImage(winNameSilhouette, imgResult);
                }
                int waitKey = cvWaitKey(33);
                if(waitKey == ‘q’) break;
                if(waitKey == ‘ ‘){
                    isStop = !isStop;
                    if(isStop) printf("stop¥n");
                    else printf("start¥n");
                }
            }

            cvReleaseCapture(&capture);
            cvDestroyWindow(winNameCapture);
            cvDestroyWindow(winNameSilhouette);

            return 0;
        }
    };
}



■C#でFormアプリを作る
新しいプロジェクトを作る。
ソリューションエクスプローラで[参照設定]→[プロジェクト参照]でShadowCameraを参照。
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う
なお、同一Solution内ならプロジェクト参照が便利だけど、
ReleaseにShadowCamera.dllができているのでそれを他の人に配布して、[ファイルを参照]してもらってdllから呼び出してもらっても同じ機能が使えます


ボタンを1つ置く。buttonStartという名前にして、ダブルクリックしてクリックイベントの雛形を作る
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う



ShadowCameraを読み込んで

using ShadowCamera;


新しいCameraクラスのインスタンスを作って、startButtonイベント内でさっき作ったrunCapture関数を呼ぶようにする
OpenCV Study: 7537970cfc6b ShadowCameraSample/ShadowCameraForm.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ShadowCamera;

namespace ShadowCameraSample
{
    public partial class ShadowCameraForm : Form
    {
        private Camera cam;
        public ShadowCameraForm()
        {
            InitializeComponent();
            cam = new Camera();
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            cam.runCapture();
        }
    }
}



ShadowCameraSampleを(右クリックして)スタートアッププロジェクトに設定し、
ビルドするとShadowCameraFormが起動する。ボタンを押すとしばらくカメラを読み込んで、数秒後にOpenCVのHighGUIのウィンドウが起動するはず。
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う

0

ahokaiに適当にtimelineの空気を読んで発言する機能、NGワード機能を追加、gemのアップデートに対応など


ahokaiという、俺の発言を収集・再構築して喋るtwitter botに

  • ruby twitter gem0.6あたりから認証方法が変わったので、現最新版(0.6.6)に対応
  • タイムラインに含まれる名詞の出現回数をチェックしてその単語から文章を作る
  • post前に発言を検閲し、NGワードが含まれていたら発言を再度作り直す
という機能追加を行った。
あとは、replyとNGユーザ機能を気の向いた時に付ける予定。


ahokaiについては橋本商会 スーパーボット大戦およびymrl.net – 裏Twitterへ。
勝手にbot化しても許してくれそうな人の発言を収集し、賛同者?の方々と共にすこしずつbotが増やしています。ありがとうございます。


■既にahokaiを動かしていて、バージョンアップしたい場合
http://bitbucket.org/shokai/bot-ahokai/ からhg pullしてhg upするか、*.rbファイルを最新版のソースコードに置き換えてください。
DBやconfig.yaml等は書き換えなくても動作します。
ruby twitter gemはアップデートして最新のものを使わなければ動きません

sudo gem update twitter –remote

windowsの場合はsudoはいりません。


—–(ここから下の機能は、設定しなくても今まで通り動くので面倒ならやらなくて良い)—–


■ruby twitter gemの認証方法の変更への対応
コンストラクタに渡す引数が変わってた → twitter gemのHTTPAuth – 橋本詳解


■タイムラインに含まれる名詞の出現回数をチェックしてその単語から文章を作る
Buzzwords.rbに実装した。
timelineをMeCabで名詞だけ抽出して、出現回数をチェックして適当に名詞を選びそこから左右にマルコフ連鎖を伸ばす。
以前実装したbuzztterのRSSから発言を作る機能を拡張したので一瞬で完成した。

config.yamlのbuzzratioという項目で頻度が設定できます。sample.config.yamlを参考にしてください。

# twitter user/pass to post
usernum : “3631571” # see your Feed of twitter
user : “username”
pass : “password”

## post with buzztter ratio
buzzratio : 0.3 # 30%

## search words for refav
#searchwords : [“ahokai”, “あほか”]

## block NG words
#blockngwords : “true”

## for debug
#nopost : “true”

0.0にすれば動作しなくなり、1.0にすると確実に空気を読みます。
なお、バズワードの取得元は自分のtimelineだけではなく、buzztterからもランダムに取得します。


■post前に発言を検閲し、NGワードが含まれていたら発言を再度作り直す
bot元にしたらbotが確実に犯罪予告しそうな人がいたので、安全な言葉を出すまで発言を再構築させて犯罪防止をする機能を追加。
ngwordsに正規表現を書いておく。

.*殺.*
.*死.*
.*糞.*
.*うんこ.*
.*爆破.*
.*爆発.*

安全な言葉を出すまで10回再提出させて、無理ならあきらめる仕様なのであまり多くすると何もしゃべれなくなる。

config.yamlで

## block NG words
#blockngwords : “true”


## block NG words
blockngwords : “true”

とコメントアウトを外せば検閲機能が有効になります。

検閲はpost3gram.rbの中で実装してます