先月末ビッグサイトにいってきた
1000分の1東京都 – a set on Flickr
Google Earthと見比べてもそっくりだった。
現在地
新宿
渋谷
かっこいい
通路で展示してた
3D地図は見慣れてるけどこれだけの画角になるとこれはこれで違う。1万x1万ピクセルぐらいのモニタでGoogle Earth起動したら似たような感覚になれるのかな
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のラッパーを実装した
UARTモジュールとまるきり同じだけど、TXだけ使うとデジタルブロック1つで済むので節約したい時に使える。
とくにCY8C21334でCapSense (CSD)使うと、digital/analogともに残り1ブロックになるので、このTX8を使うかI2Cでもう1つ別のマイコンとやりとりしてそっちにシリアル通信してもらうかしないとパソコンとやりとりできない。
9600bps出すためのCPU設定。
内蔵24MHz → System Clock → VC1 → VC3と流れて分周していってTX8_1のクロックソースにする
Port_0_1まで結線する
main.c
#include <m8c.h> // part specific constants and macrosこれだけで8bit パリティ無し ストップビット1で送信できる。
#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");
}
}
あとは秋月のFT232RL基盤のRXDピンに流せばいい
→ 橋本商会 秋月FT232RL基盤をPSoC CY8C29466で使ってみる
受信できた

橋本商会: 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になって出てくる関数を作った。
今回作っている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クラスを使うため。
「新しい参照の追加」ボタンを押す
.NETタブの下の方にスクロールしていって、System.Drawing を追加する。
ShadowCamera.hにコードを書き加える
OpenCV Study: c9320e90f1be ShadowCamera/ShadowCamera.h
DLLへの参照を追加して
using namespace System::Drawing;
勝手に書き換えられたら嫌なので、privateでグローバル変数 shadow を作っておく
private:
System::Drawing::Bitmap^ shadow;
DLL利用側のためのアクセサを用意する
public:これでDLL内のshadowというBitmapオブジェクトを、DLL外から読み出す事が出来るようになった。
System::Drawing::Bitmap^ ShadowImage(void){
return this->shadow;
}
いまいち ^ や ~ などをつけるルールを把握しきれていないけど、他のC++/CLIアプリがそうやっていたので先人の作法に倣うことにする。
あとはrunCapture()の最後のwhileループ内で、毎回作られるimgResultというIplImage(CvImage互換)オブジェクトをdynamic_castしてshadowに保存する。
this->shadow = dynamic_cast<System::Drawing::Bitmap^>長いので変換部分のみ抜粋。ShadowCamera.hの85~125行目がwhileループです
(gcnew System::Drawing::Bitmap(w,h,w*3,
System::Drawing::Imaging::PixelFormat::Format24bppRgb,
static_cast<System::IntPtr>(imgResult->imageDataOrigin)));
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にしておくと自動リサイズされて便利。

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

終わり。
ボタンじゃなくてThreadなどで適当にsleepを入れながら読み込み続ければふつうに影カメラになる。
重い画像処理を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
してリポジトリを複製してください。
- OpenCV Study: 7537970cfc6b /ShadowCamera/ : 影カメラライブラリ
- OpenCV Study: 7537970cfc6b /ShadowCameraSample/ : 影カメラライブラリを使うFormアプリケーション
影カメラライブラリの方は、OpenCV Study: 7537970cfc6b BgSubAvg/bgsubAvg.cppをDLL化したもので、cvRunningAvgを使って背景統計を取って背景差分して人型の影を作るもの → cvRunningAvgを使って背景統計を取り背景差分する – 橋本詳解
完成図はこれ。C#で作ったFormアプリで、ボタンを押すとDLLに書いてあるOpenCVの処理が起動してウィンドウが開き画像処理結果が表示/毎フレーム更新される。

■C++でDLLを作る
まず空のソリューションを作る。
ソリューション名を右クリックして新しいプロジェクト追加

VC++のクラスライブラリを作成する。名前はShadowCameraにした

できたShadowCameraプロジェクトを右クリックしてプロパティを編集

OpenCV用のライブラリを読み込む。
すべての構成->入力->追加の依存ファイル に
$(NoInherit) highgui.lib cxts.lib cv.lib cxcore.lib ml.lib cvaux.lib cvhaartraining.lib
を書く
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を参照。
なお、同一Solution内ならプロジェクト参照が便利だけど、
ReleaseにShadowCamera.dllができているのでそれを他の人に配布して、[ファイルを参照]してもらってdllから呼び出してもらっても同じ機能が使えます
ボタンを1つ置く。buttonStartという名前にして、ダブルクリックしてクリックイベントの雛形を作る
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のウィンドウが起動するはず。