Archive
以前、書いた投稿で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枚目の画像が反映されていなかったりとあります。
#それは、理論というよりもプログラム上の問題かもしれませんが

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



以前、IronPythonでimportができない例を書きました。

C#でIronPythonと連携する際にnumpyがimportできない | 詠み人知らずの備忘録



上記の対応を行うことで、DLLがロードできるため
numpyやwaveなどをimportできるようにはなったのですが、Scipyだけは下記のようにエラーが出てだめでした。

'module' object has no attribute '_getframe'

Scipy_error.png

どうやらコンソールからIronPythonを起動する際にScipyを使用するに、 -X:Frames と引数を渡していることに関係あるみたいです。

結果としては、Pythonのエンジンを起動する際に下記のようにオプションを設定して起動するように行えばいいようです。

var options = new Dictionary();
options["Frames"] = true;
options["FullFrames"] = true;
ScriptEngine engine = Python.CreateEngine(options);

※1行目と5行目がなぜかおかしくなっています。
 ただしくは、
 var options = new Dictionary<string, object>();
 です。



また、Python側でも以下のpathを追加していないとno moduleのエラーがでます。
特に、Scipyでは4行目の定義も必要のようです。
sys.path.append(r'C:\Program Files\IronPython 2.7')
sys.path.append(r'C:\Program Files\IronPython 2.7\DLLs')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib\site-packages')



C#にはどうやら音声ファイルを簡単に読み込む手段がないようで、
じゃあPythonに任せればいいのでは?


。。。ということで今回は、C#とIronPythonで音声ファイルのデータを読み込みます。


Pythonの音声ファイルの取得はこちらを参考にしました。
漫ろ草 SozoroGusa: pythonでwaveファイル(2)



ソース

C#側
using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using IronPython.Hosting;           // for IronPython
using Microsoft.Scripting.Hosting;  // for IronPython

namespace LoadWave
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void 開くOToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //「ファイルの種類」を設定
            openFileDialog1.Filter = "音声ファイル(WAV)|*.wav;|すべてのファイル(*.*)|*.*";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                // Scipyを起動するためのオプション設定
                var options = new Dictionary();
                options["Frames"] = true;
                options["FullFrames"] = true;

                ScriptEngine engine = Python.CreateEngine(options);

                ScriptSource script = engine.CreateScriptSourceFromFile("sample.py");
                ScriptScope scope = engine.CreateScope();
                
                // 選択したwavファイルをPythonに渡す値として設定
                scope.SetVariable("filename", openFileDialog1.FileName);

                // IronPythonを実行
                script.Execute(scope);

                // 処理した音声データを取得
                var audio = scope.GetVariable("audio");
            }
        }
    }
}



C#のopenFileDialogで、選択した音声ファイルをIronPythonに渡してfilenameという変数で使用しています。
// 選択したwavファイルをPythonに渡す値として設定
scope.SetVariable("filename", openFileDialog1.FileName);



IronPythonで取得した音声データ(audio)をC#側に返しています。
// 処理した音声データを取得
var audio = scope.GetVariable("audio");



IronPython側
#-*- coding: utf-8 -*-
import sys
sys.path.append(r'C:\Program Files\IronPython 2.7')
sys.path.append(r'C:\Program Files\IronPython 2.7\DLLs')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib\site-packages')

import clr
clr.AddReference('mtrand.dll')

import wave
import struct

wf = wave.open(filename, "rb")

#オーディオチャンネル数(モノラルなら1、ステレオなら2)
nchannels=wf.getnchannels()
#サンプルサイズ(バイト)
sampwidth=wf.getsampwidth()
#サンプリングレート
framerate=wf.getframerate()
#オーディオフレーム数
nframes=wf.getnframes()
#オーディオフレーム(リストオブジェクト)
frames=wf.readframes(nframes)
wf.close()

audio = struct.unpack("%dh" %nframes*nchannels, frames)



C#で配列を受け取る際にどのようにすればいいのか、特に記載はなかったのですが。
上記のようで問題ないようです。



ちなみに、正弦波の音声データを読み込んで、取得したデータを表示したものが下記のグラフです。

sin.png


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

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



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

【連載】コンピュータビジョンのセカイ - 今そこにあるミライ (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


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

C#からIronPythonを呼び出すために、調べていた際にはまったことをメモ。

簡単なスクリプトやメソッドなら呼び出せるのですがimport numpyやimport waveなどを使用すると
下記のようなエラーが・・・






メッセージ内容を見ると

IronPython.Runtime.Exceptions.ImportException はハンドルされませんでした。
Message=No module named numpy


とのこと、

DLLのロードができていないようなのですが、
他のサイトでは連携の際に上記現象の記載がない方もいらっしゃるようでこのへんは謎です。



ソース

エラーの原因は、IronPythonでロードができていないようです。
強調された箇所を追加することによって実行ができるようになりました。
#もっといい手法あるような気がしますが、調べても出てこないので。。。

ソースは以下のようになります。

Python
#-*- coding: utf-8 -*-
import sys
sys.path.append(r'C:\Program Files\IronPython 2.7')
sys.path.append(r'C:\Program Files\IronPython 2.7\DLLs')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib\site-packages')

import clr
clr.AddReference('mtrand.dll')

import numpy as np

result = np.sin(input)



C#
次にC#です。
以下の2つのDLLを参照の追加を行い。
  • IronPython.dll
  • Microsoft.Scripting.dll


その後、usingの追加を行ってください。
  • using IronPython.Hosting;
  • using Microsoft.Scripting.Hosting;
// Pythonスクリプト実行エンジン
ScriptEngine engine = Python.CreateEngine();

// 実行するPythonのソースを指定
ScriptSource source = engine.CreateScriptSourceFromFile("../../sample.py");

// 実行エンジンに渡す値を設定する
ScriptScope scope = engine.CreateScope();

// Pythonに渡す値を設定(inputという変数に40を設定)
scope.SetVariable("input", 40);

// 実行
source.Execute(scope);

// Pythonの実行結果を取得
double result = scope.GetVariable("result");




IronPythonでの計算、C#側で計算結果の受け取りができることを確認しました。
もっと良い方法があるのかもしれませんが、この辺であきらめましたorz


若干ロードがあるせいか遅い気がしますが、IronPythonのnumpyなどのライブラリがC#で使えるのは大きなメリットかなと思います。

FC2カウンター
プロフィール

詠み人知らず

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

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

カレンダー
02 | 2013/03 | 04
- - - - - 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
31 - - - - - -
最新記事
タグクラウドとサーチ

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