マイコンのプログラミングでよく使うビット演算など

すべてnは0始まり,xはunsignedと仮定.

nビット目が1かどうか判定

if(x & (1 << n))

ビットに1をセット

x |= 0xF0; // 10100110 -> 10101111

下位nビットを1で埋めるなら,これでも.

x |= (1 << n + 1) - 1;

あるいはnビット目だけを1に.

x |= (1 << n);

ビットに0をセット

下位4ビットを0で埋める.

x &= ~0x0F; // 10100110 -> 10100000

nビット目を0に.

x &= ~(1 << n);

ビットを反転

全部反転ならもちろんNOT.

x = ~x; // 00010111 -> 11101000

ある範囲だけ反転なら,反転させたい範囲を1にしたビットとのXOR.

x ^= 0x0F; // 00110011 -> 00111100

1,0しか取らない変数のとき.if(x==0) x = 1; else x = 0;よりは

x = 1 - x;

のほうが簡潔.同じ理屈でa,bしかとらない変数の値をスイッチするとき,ifを使う必要はない.

x = a + b - x;

あまり使わないかも

一番右の0を1に.

x |= x + 1; // 00100111 -> 00101111

xが2のべき乗かどうか.

if((x & (x - 1)) == 0)

一番右の1だけビットを立てる.

x & (-x); // 10101000 -> 00001000

一番右の0だけビットを立てる.

~x & (x + 1); // 10100111 -> 00001000

nビットの循環左シフト.xはX型とする.

x = (x << n)|(x >> (sizeof(X) - n); // n=2のとき 01100101 -> 10010101

OpenCVで遊んだ

同期とOpenCVやらARToolkitやらの環境を構築して遊んでみた.以下はWebカメラから動画を取り込み,リアルタイムに顔検出するコード.

参考:カメラ利用の基本形 - OpenCV@Chihara-Lab.
opencv.jp - OpenCV-1.0:CV 物体検出(Object Detection)リファレンス マニュアル -

#include <iostream>
#include <opencv/cv.h>
#include <opencv/highgui.h>

int main(int argc, char *argv[]){
	int key = 0;

	CvCapture* capture = NULL;
	IplImage* captureImage;
	IplImage* outputImage;

	if(NULL==(capture = cvCaptureFromCAM(-1)))
	{
		std::cout << "カメラが見つかりません" << std::endl;
		return -1;
	}

	char* captureWindow = "Capture";
	char* outputWindow  = "Output";
	cvNamedWindow(captureWindow,  CV_WINDOW_AUTOSIZE); 
	cvNamedWindow(outputWindow,  CV_WINDOW_AUTOSIZE);

	// 正面顔検出器の読み込み
	CvHaarClassifierCascade* cvHCC = 
		(CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_default.xml");

	// 検出に必要なメモリストレージを用意する
	CvMemStorage* cvMStr = cvCreateMemStorage(0);

	// 検出情報を受け取るためのシーケンスを用意する
	CvSeq* face;

	while(1){
		captureImage = cvQueryFrame(capture);
		outputImage = cvCloneImage(captureImage);

		// 画像中から検出対象の情報を取得する
		face = cvHaarDetectObjects
			(outputImage, cvHCC, cvMStr, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING, cvSize(50.0, 50.0));
		
		for (int i = 0; i < face->total; i++) {
			//検出情報から顔の位置情報を取得
			CvRect* faceRect = (CvRect*)cvGetSeqElem(face, 0);

			// 取得した顔の位置情報に基づき、矩形描画を行う
			cvRectangle(outputImage,
				cvPoint(faceRect->x, faceRect->y),
				cvPoint(faceRect->x + faceRect->width, faceRect->y + faceRect->height),
				CV_RGB(255, 0 ,0),
				3, CV_AA);
		}

		cvShowImage(captureWindow, captureImage);
		cvShowImage(outputWindow, outputImage);
		
		cvReleaseImage(&outputImage);
		
		key = cvWaitKey(1);
		if (key == 0x1b) break;
	}

	// 用意したメモリストレージを解放
	cvReleaseMemStorage(&cvMStr);

	// カスケード識別器の解放
	cvReleaseHaarClassifierCascade(&cvHCC);

	// キャプチャの解放
	cvReleaseCapture(&capture);

	// ウィンドウの破棄
	cvDestroyWindow(captureWindow);
	cvDestroyWindow(outputWindow);

	return 0;
}

第一感はお手軽!そしてメモリリークこえー!みたいな感じ.これは慣れだろうなあ.コードは殆どサンプルのまんま.一点だけ注意が必要なのは,cvHaarDetectObjectsの引数.静止画を弄るときと同様に引数の一部を省略し

// 画像中から検出対象の情報を取得する
face = cvHaarDetectObjects(outputImage, cvHCC, cvMStr);

と書くと,割と使い物にならないくらい遅くなる.第4〜第6引数を適切に与えることで,枝刈りが効果的に働く(詳細はリファレンス参照).調子に乗って,顔を宮崎あおいに置き換えるバージョンも作ってみた.矩形描画を画像のリサイズと合成に書き換えればOK.ちゃんと動画で実用(??)レベルの速度が出る.

参考:OpenCVで顔認識→笑い男アイコン貼り付け - ぬいぐるみライフ(仮)

		for (int i = 0; i < face->total; i++) {
			//検出情報から顔の位置情報を取得
			CvRect* faceRect = (CvRect*)cvGetSeqElem(face, 0);
			IplImage* aoi_resized = cvCreateImage(cvSize(faceRect->width, faceRect->height), outputImage->depth, outputImage->nChannels)

			cvResize(aoi, aoi_resized, CV_INTER_CUBIC);
			
			int x = faceRect->x, y = faceRect->y;
			const int i_max = ((x + aoi_resized->width ) > outputImage->width ) ? outputImage->width  - x : aoi_resized->width;
			const int j_max = ((y + aoi_resized->height) > outputImage->height) ? outputImage->height - y : aoi_resized->height;

			for (int j = 0; j < j_max; ++j){
			  for (int i = 0; i < i_max; ++i){
				int r = aoi_resized->imageData[aoi_resized->widthStep * j + i * 3];
				int g = aoi_resized->imageData[aoi_resized->widthStep * j + i * 3 + 1];
				int b = aoi_resized->imageData[aoi_resized->widthStep * j + i * 3 + 2];

				if (r || g || b) {
				  outputImage->imageData[outputImage->widthStep * (y+j) + (x+i) * 3] = r;
				  outputImage->imageData[outputImage->widthStep * (y+j) + (x+i) * 3 + 1] = g;
				  outputImage->imageData[outputImage->widthStep * (y+j) + (x+i) * 3 + 2] = b;
				}
			  }
			}
			cvReleaseImage(&aoi_resized);
		}

test what you fly, fly what you test

ここでは特に触れていなかったのですが,大学院を卒業し,4月から大阪で新社会人として働いています.Aerospace出身で精密機械系のとこで働くってのは,はてなインターン生的には異色なのかもしれませんね.そこらへんの「中途半端にあれこれ首突っ込んで,コイツ一体なにやりたいの?」的な話とか,それと絡んで修論でやったことの話とか,いずれ書きたいなあと思います.


ついでに報告すると,先日25歳になりました.あらさー….

はてなダイアリ見出しのlt,gtが消える?

先日書いたこの記事.

vector&lt;bool&gt;を使ってはいけない - Lagrangian point L2

ページ内ではタイトルが意図したとおり表示されているけど,


記事一覧で見ると…



"vectorを使ってはいけない"

ひー,めっちゃ大胆なタイトルになっとる…!
iPhone版,Web版ともにこういう挙動なので,仕様なのだ,ろうか.

人工衛星を作るときに気を付けること

人工衛星の設計って,ふつうの機器の場合と何が違うの?という質問をたまにされるので,具体例を思いつくまま挙げてみます.一般論というよりも自分の経験による部分が大きいので,JAXANASAの衛星ではこの限りではないかも.

宇宙環境の3大強敵

宇宙で動くモノをつくるにあたって,考慮すべき敵は3つ.

色んな人が色んな環境で使う自動車なんかと比べると,環境が精度よく予測出来るのである意味楽.このほかにデブリもリスクだけど,設計で回避できるようなモノではない.

電気・電子系の開発

  • fail safe, fail operativeの徹底
    • 修理が出来ない分,偶発故障に対する耐性を十分持つ必要がある.電子部品の故障モード(オープンORショート),偶発故障率(MIL-HDBK-217などを使う),システムへの影響度(FMEA)から,必要十分な対応を取る.「Aが壊れた時のためにBを用意しよう」と言うのは簡単だけど,現実的にはとても難しい.Aの故障判定やA→Bへの切り替えを行う部分を新たに用意する必要があり,今度はそこが単一故障点になるからだ.「人の手」という最終兵器が使えない以上,弱点の無い衛星システムは作れず,乱暴に言えば「どの弱点を曝すのが一番マシか」という考え方になる*1
  • 電流による磁気外乱に注意
    • 基板上を流れる電流のループは,地磁場と干渉して衛星にトルクを生む.高精度の姿勢制御が必要な小型衛星の場合,電流ループを発生させないように部品のレイアウトを工夫する必要がある.まったく同じ基板を裏表に2枚貼り,同じように動作させることで相互にトルクを打ち消すという荒業をやった衛星もあるらしい.
  • BGAやLGAパッケージは好まれない
    • 地球周回衛星は約6000秒周期の熱サイクルで温度が変動し,それに連動して基板の膨張・収縮が繰り返される.リードで応力を吸収できないこれらのパッケージは,ICにダメージを与える恐れがあるとか,長期の軌道上動作実績が無いとかで避けられる.
  • 電解コンデンサは使えない
    • 真空中で電解液が揮発する.使い物にならない.
  • 電池の充放電深度は浅く取る
    • 現在のLi-IonやNiH電池は数百サイクルの深い充放電で容量が大きく劣化する.約6000秒に1回充放電する衛星では,70日で1000サイクルに達する.数年の寿命を確保するために,大容量の電池を搭載して,数%〜十数%での浅い充放電を行う*2
  • ヒューズは使わない
    • 飛んでも交換する人がいない.自己復帰できるPTCとかなら使うことも.
  • アースが取れない
    • 衛星構造体をグラウンドとする(構体グラウンド)ことが多いが,宇宙プラズマ環境で帯電しまくるのでアースと呼べるような環境ではない.静電気放電で停止・破壊に至った衛星もある.
  • 放熱環境に注意する
    • 熱を逃がす空気が無いので,データシートの定格通りに電子部品を使うと放熱できずに焼け焦げる.計算や実験の上,必要なら金属体やヒートパイプによる放熱機構を構成する.
  • 各種法規に注意する
    • 航空宇宙機器の性質上,各国の法規にかかる可能性はある.例えば米国のEAR(再輸出規則)に沿った申告を行うなどなど.
  • 放射線による持続的影響
    • 太陽電池放射線によって発電量が落ちる.FOG(光ファイバジャイロ)は光ファイバ放射線の影響で劣化し,消費電流が増える.照射された放射線の総量に応じ,電子部品が永久破壊に至ることがある(トータルドーズ).それらの影響を考えておかないと,設計寿命の前に故障したり,電力の収支が取れなくなったりする.

機械・構造系の開発

打ち上げ時の振動を除けば,無重力なので構造的にシビアかというとそうでもない.

  • 有機材料を避ける
    • 多くの有機材料は真空中でアウトガスを放出する.ガスは電子部品や光学系に再付着し,深刻な問題を起こす.
  • 放射線による物性変化
    • 放射線の照射によって,物体の輻射率が変動することがある.衛星表面の輻射率が変わると,衛星内部の平衡温度にかなり強い影響があるので,それを織り込んだ設計をしておく必要がある.
  • 不用意に気体を封止しない
    • ハニカムパネルの内部セルや,テープ・接着剤内部に入り込んだ空気などが真空でも大丈夫かどうか,事前に漏れなく検討する.
  • 展開構造物の信頼性を徹底的に考える
    • 衛星の場合,展開してほしいものが展開しない=何もできない,という場合が多い.アンテナや太陽電池パドルなどの展開は,冗長化を徹底する箇所になる.
  • 磁性材料の管理
    • 鉄などの強磁性体は,地磁場と干渉して擾乱トルクを生む.構造から磁性材料を排除するか,逆にダンパーとして積極的に搭載するか,いずれにしても衛星の磁性は慎重に管理する必要がある.特に小型衛星の場合(慣性モーメントに対して相対的に磁気モーメントが大きいので)影響は顕著.構造の組み立てに使用する工具なども消磁作業を行い,衛星に着磁しないように注意.

ソフトウェアの開発

ハードに比べると,宇宙環境であることの影響は少ない.ただ,使うプロセッサの制限から,プログラムの空間・時間効率に強い縛りがかかることは良くある.

  • ビット反転対策
    • 高エネルギーの荷電粒子が半導体に衝突することで,ビットが反転する(Single Event Upset)ことがある.プログラム領域の反転による暴走はWDT(Watch Dog Timer)などで外部から検知するが,データ領域のビット反転はソフト的に対処する.例えば,重要なパラメータは3重に持っておき,常に多数決を取る.イメージとしてはこんな感じ.
class Foo {
public:
    int get_data() const{
        if((data_0 == data_1) && (data_1 == data_2)){
            return data_0;
        }else{
            //多数決冗長とエラー訂正
        }
    }
    void set_data(int data){
        data_0 = data_1 = data_2 = data;
    }
private:
    int data_0;
    int data_1;
    int data_2;
};
  • 変更の余地を残す
    • 例え意味的にconstであっても,後々地上からのコマンドで変更できるよう,non-constとしてRAMに展開したほうが良い場合も多い.

まとめ

何かの拍子に衛星作りたいなーと思ったら,最低限ここらへんのことを気をつけましょう.これで突然衛星を設計することになっても大丈夫ですね.

何か思い出したら適当に追加するかも.

*1:例えば,故障判定を行うチップは宇宙での動作実績が豊富なものにするとか

*2:放電深度(DOD)が浅いほど電池寿命は長いとされる

vectorを使ってはいけない

Effective STL 第18項より.

vectorSTLのコンテナとして悪い点は2つしかない.第1に,STLのコンテナではない.第2に,boolの値が格納されない.この2つを除けば,異議を唱える理由はない.(中略) vectorは疑似コンテナであり,実際にbool型の値を格納しているのではなく,スペースの節約を目的としたパック表現のbool型の値を格納しているためである.

ま,まじですか.STL使いには常識なんだろうけど,知らなかった.
effective STLでは,以下のような(コンテナであれば当然コンパイルが通るような)コードを例にとり,vectorがコンテナの要件を満たしていないことを説明している.

vector<bool> v;
bool *pb = &v[0];//vector<bool>::operator[]が返す値のアドレスでbool*を初期化したいが,コンパイルできない

パックした値のアドレスを取ろうとしているわけで,そりゃ確かに無理な話ではある.実装がどうなっているのかというと,型Tに対してvector::operator[]は通常const T&またはT&を返すけど,boolに対してはテンプレートの部分特殊化によって,以下のようにclass referenceへの参照を返す.

// 規格書23.2.5より
// http://www.kuzbass.ru:8086/docs/isocpp/lib-containers.html
namespace std {
    template <class Allocator> class vector<bool, Allocator> {
    public:
    //  types:
    typedef bool const_reference;
    //  bit reference:
    class reference {
        friend class vector;
        reference();
    public:
        ~reference();
        operator bool() const;
        reference& operator=(const bool x);
        reference& operator=(const reference& x);
        void flip();              //  flips the bit
    };

    reference       operator[](size_type n);
    const_reference operator[](size_type n) const;
};

なので,先のコードだとvector::reference*でbool*を初期化できないというコンパイルエラーを出す.さらに,boolをautoに変えると,事態はコンパイルエラーから実行時エラーに悪化する.

vector<bool> v;
auto *pb = &v[0];//これはコンパイルが通るが,期待する動作をしない

結果どういう動作になるのかというと,VCによる実装では_ITERATOR_DEBUG_LEVELが定義されているかどうかで挙動は変わる.こんな感じ.

const _Vbase *_Getptr() const
{	// get pointer to base word
 #if _ITERATOR_DEBUG_LEVEL == 2
    if (this->_Getcont() == 0
        || this->_Myptr == 0
        || 0 <= this->_Valid(0))
        {	// report error
            _DEBUG_ERROR("vector<bool> iterator not dereferencable");
            _SCL_SECURE_OUT_OF_RANGE;
        }

 #elif _ITERATOR_DEBUG_LEVEL == 1
    _SCL_SECURE_VALIDATE(this->_Getcont() != 0 && this->_Myptr != 0);
    _SCL_SECURE_VALIDATE_RANGE(this->_Valid(0) < 0);
 #endif /* _ITERATOR_DEBUG_LEVEL */

    return (this->_Myptr);
}

結局

こういう部分特殊化を行っているのはvectorだけ*1STLコンテナとして取り扱えない上に,計算コストが増えるというコストを払ってまでメモリ空間を節約したいというケースを除いて,boolのシーケンスコンテナが欲しいときはdequeあたりを使いましょう.C++こわい.

*1:そうなった歴史的経緯はeffective STLで触れられている