OpenCVでQRコード検出器を書く

このエントリーを含むはてなブックマークはてなブックマーク - OpenCVでQRコード検出器を書く

qrcodedetector0
 
 
OpenCVを使ってQRコードを検出するプログラムを作成したので、その手順をまとめてみた。このプログラムはlibdecodeqrを参考にさせていただきました。(本家のサイトは閉鎖してしまっているようです)

作成したプログラム+ドライバプログラムを置いておきますので参考にしてください。このプログラムを用いて下記にQRコード検出アルゴリズムを紹介していきます。
 
QRコード検出器プログラム
 
 
 

1. 画像中から正方形の部分を検出する

qrcodedetector1
 
まずは、ファインダパタン(3隅にある目玉画像)を検出するために、画像中から輪郭を抽出し、抽出された輪郭から正方形の輪郭のみを保存します。
 
具体的には、まずcvFindContoursメソッドを用いて輪郭情報をcont変数に格納します。次に格納された輪郭情報のうち正方形のもののみを検出するため、面積と縦横比をチェックし、そのチェックに通ったもののみcandidates変数に保存します。
 
 

cvFindContours(tmp,
				   _stor_tmp,&cont,sizeof(CvContour),
				   CV_RETR_LIST,CV_CHAIN_APPROX_NONE,cvPoint(0,0));
	
	// for marker candidates list
	CvSeq *candidates=cvCreateSeq(CV_SEQ_ELTYPE_GENERIC,
								  sizeof(CvSeq),sizeof(ImageReaderCandidate),
								  _stor_tmp);
	//
	// check each block
	//
	CvSeq *cont_head = cont;
	for(; cont; cont = cont->h_next ){
		CvRect feret = cvBoundingRect(cont);
		double area = fabs(cvContourArea(cont));
		double area_ratio = area/(double)(feret.width*feret.height);
		double feret_ratio = ((feret.width<feret.height)?
							(double)feret.width/(double)feret.height:
							(double)feret.height/(double)feret.width);
		
		//
		// search square(正方形を探索)
		//
		if(area>=MIN_AREA && area_ratio>=MIN_AREA_RATIO && feret_ratio>=MIN_FERET_RATIO){
			ImageReaderCandidate c;
			c.feret.x=feret.x;
			c.feret.y=feret.y;
			c.feret.width=feret.width;
			c.feret.height=feret.height;
			c.contour=cont;
			cvSeqPush(candidates, &c);
		}
		else{
			cvClearSeq(cont);
		}
	}

 
 
 

2. ファインダパタンの検出

qrcodedetector2
 
Step1で検出された正方形の中から、ファインダパタンを見つけるため、正方形の中にもう一つ正方形が含まれている箇所を検出し、seq_finder_patternに追加します。
 
ファインダパタンの位置を示す型としてCvBox2D型を使用しています。このCvBox2D型はCvRect型と同様、長方形を表す型ですが、CvBox2D型では回転まで考慮できるため傾いた長方形にも対応できるのがCvRect型との違いです。
 
 

	for(i = 0; i < candidates->total; i++){
		ImageReaderCandidate *cand1= (ImageReaderCandidate *)cvGetSeqElem(candidates,i);
		
		int inner_contour=0;
		int j;
		for(j = 0; j < candidates->total; j++){
			if(i == j) continue;
			
			ImageReaderCandidate *cand2 = (ImageReaderCandidate *)cvGetSeqElem(candidates,j);
			CvRect max_rect=cvMaxRect(&(cand1->feret),&(cand2->feret));
			if(cand1->feret.x==max_rect.x&&
			   cand1->feret.y==max_rect.y&&
			   cand1->feret.width==max_rect.width&&
			   cand1->feret.height==max_rect.height)
				inner_contour++;
		}
		
		//
		// There were 2 squires (white and black) inside a squire,
		// the most outer squire assumed as position marker.
		//
		if(inner_contour==2){
			CvBox2D box = cvMinAreaRect2(cand1->contour);
			cvSeqPush(seq_finder_pattern, &box);			
		}
	}

 
 
 

3. QRコードの輪郭を抽出する

 qrcodedetector3
 
検出された3つのファインダパタンを用いてQRコードの輪郭を抽出します。ここではQRコードの輪郭を、ファインダパタンをすべて含むような矩形として定義しています。
 
具体的には、cvBoxPoints関数を用いて、ファインダパタンを示すCvBox2D型の変数から4つの頂点座標を取得し、合計12個の頂点座標を含むような矩形をcvMinAreaRect2関数で作成します。最後に再びcvBoxPoints関数を使って頂点座標を検出することでQRコードの4つの頂点座標を取得しています。
 
 

	int c = seq_finder_pattern->total, i;
	for(i = 0; i < c; i++){
		
		box = *(CvBox2D *)cvGetSeqElem(seq_finder_pattern,i);
		_finderpattern_boxes&#91;i&#93; = box;

		cvBoxPoints(box, pt_32f);
		for(int j = 0; j < 4; j++){
			CvPoint p = cvPointFrom32f(pt_32f&#91;j&#93;);
			cvSeqPush(markers_vertex,&p);
		}
	}
	
	//
	// create Minimal-area bounding rectangle which condist
	// every position makers
	// 点列を包含する最小矩形をboxに格納
	//
	box = cvMinAreaRect2(markers_vertex);
	cvRelease((void **)&markers_vertex);
	
	//
	// create code area mask
	// pointsに四隅の座標がはいる
	//
	cvBoxPoints(box, pt_32f);
	CvPoint *points=new CvPoint&#91;4&#93;;
	for(i = 0; i < 4; i++){
		//squarePoints&#91;i&#93;=cvPointFrom32f(pt_32f&#91;i&#93;);
		points&#91;i&#93; = cvPointFrom32f(pt_32f&#91;i&#93;);	
	}

&#91;/cpp&#93;
 
 
 
<span style="font-size:large;color:RoyalBlue"><strong>
4. QRコード部分のみをトリミングする
</strong></span>
<a href="http://iphone.moo.jp/app/wp-content/uploads/g4.jpg"><img src="http://iphone.moo.jp/app/wp-content/uploads/g4.jpg" alt="qrcodedetector4" title="qrcodedetector4" width="150" height="150" class="alignnone size-full wp-image-919" /></a>
 
最後に、検出されたQRコードの4頂点から正方形への射影変換を行います。OpenCVを使った射影変換のコードはここを参考にさせていただきました。
 
<a href="http://chihara.naist.jp/opencv/?%BC%CD%B1%C6%CA%D1%B4%B9">射影変換 | OpenCV@Chihara-Lab.</a>
 
[cpp]
	for(int i = 0; i < 4; ++i){
		a&#91;j++&#93; = p&#91;i&#93;.x;
		a&#91;j++&#93; = p&#91;i&#93;.y;		
	}
    src_point = cvMat( 4, 2, CV_64FC1, a );
	
    CvMat    dst_point;
    double    b&#91;&#93; = {
        0, 0,
        0, dst->height - 1,
        dst->width - 1, dst->height - 1,
        dst->width - 1, 0
    };
    dst_point = cvMat( 4, 2, CV_64FC1, b );
	
    CvMat    *h = cvCreateMat( 3, 3, CV_64FC1 );	
    cvFindHomography( &src_point, &dst_point, h );
    cvWarpPerspective( src, dst, h );

 
 
 

5. まとめ

OpenCVでQRコードの検出器を実装してみました。検出の流れとしては以下のようになります。

  • 画像中かの輪郭情報を取得し
  • 得られた輪郭情報から正方形を探索
  • 正方形が2重になっているものがファインダパタン
  • ファインダパタンから4頂点を決定
  • その頂点座標を用いて射影変換

 
 
その他の検出方法
 
QRコードを機械学習から検出しちゃおうという面白いサイトがありましたので、ここに紹介しておきます。Haar cascadeも公開されているので一度試してみてはいかがでしょうか?

QRコード検出 | CanI’s Diary

Leave a Reply

You must be logged in to post a comment.