よく位相限定相関(POC)をキーワードでブログにいらっしゃる方がいるのと、
自分のためにまとめておきます。


理論
位相限定相関法についての求め方を書いてあります。

また、4ではOpenCVで実装されているものの紹介。
5では従来の手法では検出できない回転に対して検出するための前処理であるLog-Polar変換を書いております。

  1. 位相画像
  2. 位相限定相関法(POC)
  3. 位相限定相関(POC)でサブピクセル精度を求める方法
  4. 位相限定相関(POC)がOpenCVで実装されている
  5. Log-Polar変換を画像に適用



OpenCvSharp
OpenCvSharpで実装してみたものです。。。があまり自信はありません。
  1. 位相画像をC#とOpenCVSharpで実装
  2. 位相限定相関法(POC)をOpenCVSharpで



画像補間について
回転がされている場合でも検出できるようにLog-Polar変換の時に必要であったため記載したものです。
ですが、有名な補間手法なので画像の回転・拡大・縮小に使えます。

  1. 画像を回転させる際の画素を補間するコード
  2. 回転した画像をLanczos補間する
  3. 回転した画像をバイキュービック補間する
  4. 回転した画像をバイリニア補間する
  5. 画像を回転させる



何か進展があればまた追記していきます。


以前、書いた投稿でSURFで二枚の画像での対応付けを行いました。

PythonからOpenCVのSURFを使う | 詠み人知らずの備忘録



SURFで抽出した対応する座標を用いれば、画像同士を合成することができます。
以下に参考にしたサイトです。


2枚の画像のモザイキング | Miyabiarts.net
前に紹介した「OpenCVを用いた特徴点対応付け」を応用することで複数の画像をモザイキングすることができます。 今回は、OpenCVを用いて下に示す2枚の画像をモザイキングして、1枚の大きな画像を作ります。 今回の例では、2枚の画像が大体半分程度重なり合ってないと上手くいかないです。 あと、画像合成の関係上、1枚目の画像に対して2枚目の画像が左側になるように撮影しないと正しい結果が得られませんので気をつけてください。



Panorama – Image Stitching in OpenCV | ramsrigoutham
he code snippet shown below is for simple image stitching of two images in OpenCV . It can easily be modified to stitch multiple images together and create a Panorama. OpenCV also has a stitching module which helps in achieving this task and which is more robust than this. The code presented here will help in understanding the major steps involved in image stitching algorithm. I am using OpenCV 2.4.3 and Visual studio 2010. This code is based on the openCV tutorial available here.



SURFを実行しても、surf結果画像のように完璧に対応点を抽出できません。 いくつか、誤検出が発生してしまいます。


surf結果画像
surf.png


そのため、誤検出を取り除く処理が必要になります。
RANSACが有名な方法のようです。

画像を合成する際に移動および回転などをさせるために射影変換を行いますが、OpenCVのfindhomographyでは、CV_RANSACを第3引数にすることにより、対応点をRANSACでふるいにかけてくれるようです。

ソース

途中まではSURFを求めるまでは前回までと同じソースを使用しています。
その座標を用いてHomography行列を求め合成しています。

# -*- coding: shift_jis -*-
# matching features of two images
import cv2
import sys
import scipy as sp
import numpy

if len(sys.argv) < 3:
    print 'usage: %s img1 img2' % sys.argv[0]
    sys.exit(1)

img1_path = sys.argv[1]
img2_path = sys.argv[2]

img1 = cv2.imread(img1_path, cv2.CV_LOAD_IMAGE_GRAYSCALE)
img2 = cv2.imread(img2_path, cv2.CV_LOAD_IMAGE_GRAYSCALE)

detector = cv2.FeatureDetector_create("SURF")
descriptor = cv2.DescriptorExtractor_create("BRIEF")
matcher = cv2.DescriptorMatcher_create("BruteForce-Hamming")

# detect keypoints
kp1 = detector.detect(img1)
kp2 = detector.detect(img2)

print '#keypoints in image1: %d, image2: %d' % (len(kp1), len(kp2))

# descriptors
k1, d1 = descriptor.compute(img1, kp1)
k2, d2 = descriptor.compute(img2, kp2)

print '#keypoints in image1: %d, image2: %d' % (len(d1), len(d2))

# match the keypoints
matches = matcher.match(d1, d2)

# visualize the matches
print '#matches:', len(matches)
dist = [m.distance for m in matches]

print 'distance: min: %.3f' % min(dist)
print 'distance: mean: %.3f' % (sum(dist) / len(dist))
print 'distance: max: %.3f' % max(dist)

# threshold: half the mean
thres_dist = (sum(dist) / len(dist)) * 0.9

# keep only the reasonable matches
sel_matches = [m for m in matches if m.distance < thres_dist]

print '#selected matches:', len(sel_matches)

point1 = [[k1[m.queryIdx].pt[0], k1[m.queryIdx].pt[1]] for m in sel_matches]
point2 = [[k2[m.trainIdx].pt[0], k2[m.trainIdx].pt[1]] for m in sel_matches]

point1 = numpy.array(point1)
point2 = numpy.array(point2)

H, Hstatus = cv2.findHomography(point2,point1,cv2.RANSAC)

# 移動量を算出
x=0
y=0
cnt=0
for i,v in enumerate(Hstatus):
    if v==1:
        x += point1[i][0]-point2[i][0]
        y += point1[i][1]-point2[i][1]
        cnt += 1

# カラー画像として改めて読み込む
img1 = cv2.imread(img1_path)
img2 = cv2.imread(img2_path)

x = abs(int(round(x/cnt)))
y = abs(int(round(y/cnt)))

# sizeを取得
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]

dst = cv2.warpPerspective(img2,H,(w2+x,h2+y))

for i in xrange(w1):
    for j in xrange(h1):
        dst[j,i] = img1[j,i]

cv2.imshow("result", dst)
cv2.waitKey()



結果

入力画像
wall1.jpg


wall2.jpg


2枚目画像のHomography行列と演算結果
homography1.png


結果画像
homography2.png


2枚目画像のHomography行列と演算結果を見て分かるかもしれませんが、気づいたことを何点か・・・

このプログラムでは、1枚目の画像に合わせるように2枚目の画像を射影変換して合わせているため、 画像の左右の対応関係が逆だったりすると合成がうまくいかなかったり。
1枚目の画像より上部にある2枚目の画像が反映されていなかったりとあります。
#それは、理論というよりもプログラム上の問題かもしれませんが

ただ、複数枚つなげていくと誤差が積み重なっていきそうではあります。



巷で大人気の漫画カメラというソフトがあります。

漫画カメラ | スマートフォン カメラアプリ | スーパーソフトウエア



その漫画カメラの実装について、こちらにわかりやすい解説を見つけました。

【連載】コンピュータビジョンのセカイ - 今そこにあるミライ (39) iPhoneアプリ「漫画カメラ」に見るコンピュータビジョンの実応用例 | 開発・SE | マイナビニュース

【連載】コンピュータビジョンのセカイ - 今そこにあるミライ (40) iPhoneアプリ「漫画カメラ」で使われている画像処理手法その1 | 開発・SE | マイナビニュース

【連載】コンピュータビジョンのセカイ - 今そこにあるミライ (41) iPhoneアプリ「漫画カメラ」で使われている画像処理手法その2 | 開発・SE | マイナビニュース



こちらで具体的に、iOSで実装されている方がおります。

OpenCVで写真を漫画風に加工しよう 〜実装編〜 | Developers.IO



ソース

ということで、今回は漫画カメラ風の画像を作り出すのをやってみました。
ちなみに、PythonとOpenCVで実装しています。
手順は以下の通りです。
  1. 画像を読み込む
  2. Cannyのエッジ検出を行い、その画像をネガポジ変換を行う。
  3. 3値化処理を行う
  4. 3値化処理の灰色に割り当てられたところをスクリーントーンに置き換える
  5. 2,4の画像を合成する


ちなみに、2,3をいっぺんに行ったり、
5の合成の後に灰色部分を消すため再度2値化処理を行ったり色々とアレ・・・です。。。


スクリーントーンはこちらのサイトからいただいたものを使用しました。
WEB用無料素材 | デジタルトーンスジタオ




import cv2
import numpy

# 画像を読み込む
img = cv2.imread("Lenna.bmp",cv2.CV_LOAD_IMAGE_GRAYSCALE)
screen = cv2.imread("screen.bmp",cv2.CV_LOAD_IMAGE_GRAYSCALE)

# エッジ検出後にネガポジ反転
edge= cv2.Canny(img,80,120)
nega = cv2.bitwise_not(edge)

# sizeを取得
width = img.shape[0] 
height = img.shape[1]

# 3値化を行う(ただし灰色の場合はスクリーントーンに置き換える)
temp = numpy.zeros_like(img)
for i in xrange(width):
    for j in xrange(height):
        if img[i,j]<80:
            temp[i,j] = 0
        elif img[i,j]>=80 and img[i,j]<160:
            temp[i,j] = screen[i,j]
        else:
            temp[i,j] = 255

# 3値化とエッジの画像を合成
alpha = 0.5
result = cv2.addWeighted(nega,alpha,temp,1-alpha,0.0)
(thresh,result)=cv2.threshold(result,200,255,cv2.THRESH_BINARY)



結果

結果は以下になります。。。


【元画像】
Lenna



【エッジ画像(Canny)】
edge.png



【ネガポジ変換後】
nega.png



【3値化画像】
3val.png



【ネガポジ+3値化=漫画風】
result_20130320013925.png



反省点

画像の大きさに合わせてスクリーントンのサイズを変えたり、
スクリーントンのパターンを変更できるようにしたいですね。

あとは、画像合成のうまい方法忘れてしまいました。。。orz

お久しぶりです。
今回は普通(?)のOpencvとPythonです。
#OpenCVSharpとの兼ね合いのため、私の環境のOpenCVのバージョンが2.4.0となっておりますので、ご注意願いますm(_ _)m



導入

OpenCVはインストールされていると仮定してですが、
Pythonで動かすにはもう一手間必要です。

C:\opencv\build\python\2.7 にある
cv2.pyd というファイルを下記のフォルダにコピーします。

C:\Python27\Lib\site-packages
#フォルダ名などの違いは各自のフォルダに読み替えて実行願います。



ソース

SURFの実行方法を調べていたところ、下記サイトで相談&回答されています。


How to visualize descriptor matching using opencv module in python - Stack Overflow

ただ、このまま実行したところ、エラーが生じたため。その部分(65行目)を修正しました。
# matching features of two images
import cv2
import sys
import scipy as sp

if len(sys.argv) < 3:
    print 'usage: %s img1 img2' % sys.argv[0]
    sys.exit(1)

img1_path = sys.argv[1]
img2_path = sys.argv[2]

img1 = cv2.imread(img1_path, cv2.CV_LOAD_IMAGE_GRAYSCALE)
img2 = cv2.imread(img2_path, cv2.CV_LOAD_IMAGE_GRAYSCALE)

detector = cv2.FeatureDetector_create("SURF")
descriptor = cv2.DescriptorExtractor_create("BRIEF")
matcher = cv2.DescriptorMatcher_create("BruteForce-Hamming")

# detect keypoints
kp1 = detector.detect(img1)
kp2 = detector.detect(img2)

print '#keypoints in image1: %d, image2: %d' % (len(kp1), len(kp2))

# descriptors
k1, d1 = descriptor.compute(img1, kp1)
k2, d2 = descriptor.compute(img2, kp2)

print '#keypoints in image1: %d, image2: %d' % (len(d1), len(d2))

# match the keypoints
matches = matcher.match(d1, d2)

# visualize the matches
print '#matches:', len(matches)
dist = [m.distance for m in matches]

print 'distance: min: %.3f' % min(dist)
print 'distance: mean: %.3f' % (sum(dist) / len(dist))
print 'distance: max: %.3f' % max(dist)

# threshold: half the mean
thres_dist = (sum(dist) / len(dist)) * 0.5

# keep only the reasonable matches
sel_matches = [m for m in matches if m.distance < thres_dist]

print '#selected matches:', len(sel_matches)

# #####################################
# visualization
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
view = sp.zeros((max(h1, h2), w1 + w2, 3), sp.uint8)
view[:h1, :w1, 0] = img1
view[:h2, w1:, 0] = img2
view[:, :, 1] = view[:, :, 0]
view[:, :, 2] = view[:, :, 0]

for m in sel_matches:
    # draw the keypoints
    # print m.queryIdx, m.trainIdx, m.distance
    color = tuple([sp.random.randint(0, 255) for _ in xrange(3)])
    cv2.line(view, (int(k1[m.queryIdx].pt[0]),int(k1[m.queryIdx].pt[1])), (int(k2[m.trainIdx].pt[0] + w1), int(k2[m.trainIdx].pt[1])), color)

cv2.imshow("view", view)
cv2.waitKey()



実行結果

結果は以下のようになります。

result.png


無事結果が表示されました ε-(´∀`*)ホッ

一生懸命POCを実装していましたが、
OpenCV内にphaseCorrelateとしてPOCが定義されているのを発見しました。

ヘルプなどあさってみましたが特に記載されていないため、
まだベータ版なのかもしれませんが問題なく動作していたためご紹介を

opencvフォルダ内の以下のディレクトリにそれぞれ格納されていました。
関数自体の定義
modules\imgproc\src\phasecorr.cpp
モジュール(?)のテスト
modules\imgproc\test\test_pc.cpp
使用方法
samples\cpp\phase_corr.cpp



ソース

phase_corr.cppをそのまま転載します。

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace cv;

int main(int, char* [])
{
    VideoCapture video(0);
    Mat frame, curr, prev, curr64f, prev64f, hann;
    int key = 0;

    do
    {
        video >> frame;
        cvtColor(frame, curr, CV_RGB2GRAY);

        if(prev.empty())
        {
            prev = curr.clone();
            createHanningWindow(hann, curr.size(), CV_64F);
        }

        prev.convertTo(prev64f, CV_64F);
        curr.convertTo(curr64f, CV_64F);

        Point2d shift = phaseCorrelate(prev64f, curr64f, hann);
        double radius = cv::sqrt(shift.x*shift.x + shift.y*shift.y);

        if(radius > 5)
        {
            // draw a circle and line indicating the shift direction...
            Point center(curr.cols >> 1, curr.rows >> 1);
            cv::circle(frame, center, (int)radius, cv::Scalar(0, 255, 0), 3, CV_AA);
            cv::line(frame, center, Point(center.x + (int)shift.x, center.y + (int)shift.y), cv::Scalar(0, 255, 0), 3, CV_AA);
        }

        imshow("phase shift", frame);
        key = waitKey(2);

        prev = curr.clone();
    } while((char)key != 27); // Esc to exit...

    return 0;
}



27行目がPOCです。画像を渡すとPoint2dで座標が返却されます。
動作内容はカメラから画像(フレーム)を取得し、前のフレームと比較しカメラがどちらに動いているか判定するようです。
移動方向をフレーム中心の円の大きさを用いて表示するようになっております。

参考までにご紹介です。
FC2カウンター
プロフィール

詠み人知らず

Author:詠み人知らず
プログラム好きな名もなき凡人がお送りしています。(得意とは言っていない
最近の興味はPython、C#、Matlab(Octave)、画像処理、AR(拡張現実)、統計などなど・・・

気分で思いついたことを書くため話題に一貫性がないかもしれません。

カレンダー
05 | 2017/06 | 07
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -
最新記事
タグクラウドとサーチ

カテゴリ
最新コメント
最新トラックバック
月別アーカイブ