2017-06-09 18 views
1

テンプレートマッチングを使用して2つの画像を縫い合わせようとしていますcv2.getAffineTransform()に渡す3点のセットを見つけてcv2.warpAffine()に渡して画像を整列させます。warpAffineを使用して切り取らずにステッチ画像を表示する

私の画像に参加すると、私のアフィニティ画像の大部分は表示されません。私は点を選択するためにさまざまなテクニックを使って試しました、順序や引数などを変更しましたが、アフィン画像の薄いスライダーを表示することしかできません。

誰かが私のアプローチが有効なものかどうかを教えてもらえますか?エラーが発生している可能性がある場所を教えてください。どのような問題が発生する可能性がありますかについてのすべての推測は非常に高く評価されるだろう。前もって感謝します。

これは私が得たfinal resultです。

EDIT:ここでは、元の画像(12)と私が使用するコードです。ここ変数trans

array([[ 1.00768049e+00, -3.76690353e-17, -3.13824885e+00], 
     [ 4.84461775e-03, 1.30769231e+00, 9.61912797e+02]]) 

そしてここでの結果は、ここでcv2.getAffineTransformに渡されるポイントであるです:unified_pair1

array([[ 671., 1024.], 
     [ 15., 979.], 
     [ 15., 962.]], dtype=float32) 

unified_pair2

array([[ 669., 45.], 
     [ 18., 13.], 
     [ 18., 0.]], dtype=float32) 

import cv2 
import numpy as np 


def showimage(image, name="No name given"): 
    cv2.imshow(name, image) 
    cv2.waitKey(0) 
    cv2.destroyAllWindows() 
    return 

image_a = cv2.imread('image_a.png') 
image_b = cv2.imread('image_b.png') 


def get_roi(image): 
    roi = cv2.selectROI(image) # spacebar to confirm selection 
    cv2.waitKey(0) 
    cv2.destroyAllWindows() 
    crop = image_a[int(roi[1]):int(roi[1]+roi[3]), int(roi[0]):int(roi[0]+roi[2])] 
    return crop 
temp_1 = get_roi(image_a) 
temp_2 = get_roi(image_a) 
temp_3 = get_roi(image_a) 

def find_template(template, search_image_a, search_image_b): 
    ccnorm_im_a = cv2.matchTemplate(search_image_a, template, cv2.TM_CCORR_NORMED) 
    template_loc_a = np.where(ccnorm_im_a == ccnorm_im_a.max()) 

    ccnorm_im_b = cv2.matchTemplate(search_image_b, template, cv2.TM_CCORR_NORMED) 
    template_loc_b = np.where(ccnorm_im_b == ccnorm_im_b.max()) 
    return template_loc_a, template_loc_b 


coord_a1, coord_b1 = find_template(temp_1, image_a, image_b) 
coord_a2, coord_b2 = find_template(temp_2, image_a, image_b) 
coord_a3, coord_b3 = find_template(temp_3, image_a, image_b) 

def unnest_list(coords_list): 
    coords_list = [a[0] for a in coords_list] 
    return coords_list 

coord_a1 = unnest_list(coord_a1) 
coord_b1 = unnest_list(coord_b1) 
coord_a2 = unnest_list(coord_a2) 
coord_b2 = unnest_list(coord_b2) 
coord_a3 = unnest_list(coord_a3) 
coord_b3 = unnest_list(coord_b3) 

def unify_coords(coords1,coords2,coords3): 
    unified = [] 
    unified.extend([coords1, coords2, coords3]) 
    return unified 

# Create a 2 lists containing 3 pairs of coordinates 
unified_pair1 = unify_coords(coord_a1, coord_a2, coord_a3) 
unified_pair2 = unify_coords(coord_b1, coord_b2, coord_b3) 

# Convert elements of lists to numpy arrays with data type float32 
unified_pair1 = np.asarray(unified_pair1, dtype=np.float32) 
unified_pair2 = np.asarray(unified_pair2, dtype=np.float32) 

# Get result of the affine transformation 
trans = cv2.getAffineTransform(unified_pair1, unified_pair2) 

# Apply the affine transformation to original image 
result = cv2.warpAffine(image_a, trans, (image_a.shape[1] + image_b.shape[1], image_a.shape[0])) 
result[0:image_b.shape[0], image_b.shape[1]:] = image_b 

showimage(result) 
cv2.imwrite('result.png', result) 

出典:アプローチの助言に基づいてドキュメントからhere、このtutorialこのexampleを受けました。

+1

最後の質問からソリューションを実装しています。提案のために、人々はあなたのコードのすべてを調べる必要がないので、あなたが作成した変換を含め、正しい座標であなたを得るためにその変換を取得しようとするコードだけを表示することができます。私はあなたがすぐに答えを得ることができなければならないので、それが必要かどうかはわかりませんが。主な問題は、チュートリアルでは、左から右に向かっていると仮定しています。シフトするピクセル数は実際には何ピクセルかわかりません。あなたは実際にこれを計算することができます。 –

+0

@AlexanderReynoldsあなたに感謝してくれてありがとう、私はwarpMatrixを生成するために使用された変数が元の質問だけでなく、変換行列 'trans'に追加されたものを追加しました。 'cv2.getAffineTransformation'によって生成された変換行列も画像をシフトすると仮定しました。シフトを計算する方法について知っていますか? – Bprodz

+1

はい、あります。今すぐ答えを書く。 :) –

答えて

3

7月12日編集:この投稿は、このタスクを達成するための機能を提供するGitHubのレポを触発

。 1つはパッド付きwarpAffine()用であり、もう1つはパッド付きwarpPerspective()用です。 Python versionまたはC++ versionをフォークします。


変換は

何任意の変換がないですが、あなたのポイントを取る(x, y)を調整し、新しい場所(x', y')にマップのピクセル

の位置をずらす: sは、いくつかのスケーリング係数である

s*x' h1 h2 h3  x 
s*y' = h4 h5 h6 * y 
s  h7 h8 1  1 

。適切なピクセル位置(x', y')を取り戻すには、新しい座標をスケール係数で除算する必要があります。技術的には、これはホモグラフィの場合にのみ当てはまります--- (3, 3)変換行列---アフィン変換のためにスケーリングする必要はありません(均質な座標を使う必要はありませんが、 。

次に、実際のピクセル値が新しい場所に移動され、色の値が新しいピクセルグリッドに合わせて補間されます。この過程で、これらの新しい場所がある時点で記録されます。ピクセルが実際にどこに移動するかを他の画像と比較して確認するには、それらの位置が必要です。簡単な例から始めて、ポイントがマップされている場所を見てみましょう。

変換行列がピクセルを10ピクセルだけ左に移動するとします。翻訳は最後の列で処理されます。最初の行はxの翻訳であり、2番目の列はyの翻訳です。だから、恒等行列を持ちますが、最初の行、第3列には-10があります。ピクセル(0,0)はどこにマップされますか?うまくいけば、論理が意味をなさなければ(-10,0)。実際には、以下のようになります。

transf = np.array([[1.,0.,-10.],[0.,1.,0.],[0.,0.,1.]]) 
homg_pt = np.array([0,0,1]) 
new_homg_pt = transf.dot(homg_pt)) 
new_homg_pt /= new_homg_pt[2] 
# new_homg_pt = [-10. 0. 1.] 

パーフェクト!だから、すべて小さな線形代数を使って点のマップを見つけることができます。 (x,y)ポイントをすべて取得し、それらを1つ1つのポイントがそれ自身の列になるように巨大な配列に配置する必要があります。私たちのイメージがたったの4x4のふりをします。

h, w = src.shape[:2] # 4, 4 
indY, indX = np.indices((h,w)) # similar to meshgrid/mgrid 
lin_homg_pts = np.stack((indX.ravel(), indY.ravel(), np.ones(indY.size))) 

これらlin_homg_ptsは今、すべての均質なポイントがあります。

[[ 0. 1. 2. 3. 0. 1. 2. 3. 0. 1. 2. 3. 0. 1. 2. 3.] 
[ 0. 0. 0. 0. 1. 1. 1. 1. 2. 2. 2. 2. 3. 3. 3. 3.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]] 

その後、我々はすべての点のマッピングされた値を取得するには、マトリックス乗算を行うことができます。簡単にするために、以前の同音異義語に固執しましょう。

trans_lin_homg_pts = transf.dot(lin_homg_pts) 
trans_lin_homg_pts /= trans_lin_homg_pts[2,:] 

そして今、我々は、変換された点を持っている:私たちが見ることができるように

[[-10. -9. -8. -7. -10. -9. -8. -7. -10. -9. -8. -7. -10. -9. -8. -7.] 
[ 0. 0. 0. 0. 1. 1. 1. 1. 2. 2. 2. 2. 3. 3. 3. 3.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]] 

を、すべてが期待どおりに動作している:我々は-10で、唯一x - 値をシフトしています。

ピクセルは、外部のイメージの境界のこれらのピクセル位置は、彼らがイメージの境界の外にいる---マイナスであることを

お知らせをシフトさせることができます。もう少し複雑な作業を行い、画像を45度回転させると、元の範囲外のピクセル値が得られます。しかし、すべてのピクセル値を気にする必要はありません。最も遠いピクセルが元のイメージピクセル位置の外側にあるかどうかを知る必要があるだけです。ワープされたイメージを表示する前に元のイメージを埋め込むことができます。

私たちは、元の画像の外側で、正と負の両方向にピクセル位置を十分に取得できることがわかります。ホモグラフィで回転を適用すると、左上隅から回転が適用されるため、最小値はxです。ここで注目すべきことは、画像のすべてのピクセルに変換を適用したことです。しかし、これは本当に不必要です、あなたは単純に4つのコーナーポイントを歪ませ、彼らがどこに着陸するか見ることができます。あなたはcv2.warpAffine()呼び出すときに、入力先のサイズに持っていることを先の画像

注意をパディング

。これらの変換されたピクセル値はそのサイズを参照します。したがって、ピクセルが(-10,0)にマップされると、そのピクセルは宛先イメージに表示されません。つまり、すべてのピクセルの位置が正の値になるような変換を行って別のホモグラフィを作成しなければならないことを意味します。ホモグラフィが画像よりも大きな位置にポイントを移動する場合は、元の画像を下側と右側に貼り付けなければなりません。

最近の例では、最小値xは同じ値なので、水平シフトは不要です。しかし、最小値yの値が約2ピクセル下がったので、イメージを2ピクセル下にシフトする必要があります。まず、埋め込まれた宛先イメージを作成しましょう。

pad_sz = list(src.shape) # in case three channel 
pad_sz[0] = np.round(np.maximum(pad_sz[0], maxY) - np.minimum(0, minY)).astype(int) 
pad_sz[1] = np.round(np.maximum(pad_sz[1], maxX) - np.minimum(0, minX)).astype(int) 
dst_pad = np.zeros(pad_sz, dtype=np.uint8) 
# pad_sz = [6, 4, 3] 

私が見ることができるように、高さは、そのシフトを説明するために元のピクセルから2ピクセル増加しました。

今正

に、すべてのピクセル位置をシフトするために、変換に翻訳を追加し、我々はによってシフトし、同じ量によって歪められた画像を変換するために、新たなホモグラフィ行列を作成する必要があります。そして、両方の変換---オリジナルと今回の新しいシフト---を適用するには、アフィン変換のために、を単純に追加しますが、ホモグラフィは追加しないでください)。さらに、我々はスケールはまだ(再び、唯一のホモグラフィのために)適切であることを確認するために、最後のエントリで分割する必要があります。

anchorX, anchorY = 0, 0 
transl_transf = np.eye(3,3) 
if minX < 0: 
    anchorX = np.round(-minX).astype(int) 
    transl_transf[0,2] -= anchorX 
if minY < 0: 
    anchorY = np.round(-minY).astype(int) 
    transl_transf[1,2] -= anchorY 
new_transf = transl_transf.dot(transf) 
new_transf /= new_transf[2,2] 

私もここで我々がパディングに先の画像を配置する場所のためのアンカーポイントを作成しますマトリックス;ホモグラフィがイメージをシフトする同じ量だけシフトされます。それでは、パッド入りのマトリックス内のデスティネーションイメージを配置しましょう:パッド入り画像

への新たな転換と

dst_pad[anchorY:anchorY+dst_sz[0], anchorX:anchorX+dst_sz[1]] = dst 

ワープ私たちがしなければ残っているすべては、パディングと(元画像に新しい変換を適用でデスティネーションサイズ)、2つのイメージをオーバーレイすることができます。

warped = cv2.warpPerspective(src, new_transf, (pad_sz[1],pad_sz[0])) 

alpha = 0.3 
beta = 1 - alpha 
blended = cv2.addWeighted(warped, alpha, dst_pad, beta, 1.0) 

することは、我々は我々がここで終わりにする必要はありませんかなりの数の変数を作成して以来のは、このための関数を作成してみましょう一緒に

それをすべてを置きます。入力には、ソースイメージ、デスティネーションイメージ、オリジナルホモグラフィが必要です。また、出力のためには、パディングされた宛先イメージとワープされたイメージが必要です。例では3x3のホモグラフィを使用していたので、2x3アファインまたはユークリッドのワープの代わりに3x3の変換を送信するようにしてください。下のアフィンワープにローの[0,0,1]を追加するだけで、うまくいくでしょう。

最後の関数を実行しているの

def warpPerspectivePadded(img, dst, transf): 

    src_h, src_w = src.shape[:2] 
    lin_homg_pts = np.array([[0, src_w, src_w, 0], [0, 0, src_h, src_h], [1, 1, 1, 1]]) 

    trans_lin_homg_pts = transf.dot(lin_homg_pts) 
    trans_lin_homg_pts /= trans_lin_homg_pts[2,:] 

    minX = np.min(trans_lin_homg_pts[0,:]) 
    minY = np.min(trans_lin_homg_pts[1,:]) 
    maxX = np.max(trans_lin_homg_pts[0,:]) 
    maxY = np.max(trans_lin_homg_pts[1,:]) 

    # calculate the needed padding and create a blank image to place dst within 
    dst_sz = list(dst.shape) 
    pad_sz = dst_sz.copy() # to get the same number of channels 
    pad_sz[0] = np.round(np.maximum(dst_sz[0], maxY) - np.minimum(0, minY)).astype(int) 
    pad_sz[1] = np.round(np.maximum(dst_sz[1], maxX) - np.minimum(0, minX)).astype(int) 
    dst_pad = np.zeros(pad_sz, dtype=np.uint8) 

    # add translation to the transformation matrix to shift to positive values 
    anchorX, anchorY = 0, 0 
    transl_transf = np.eye(3,3) 
    if minX < 0: 
     anchorX = np.round(-minX).astype(int) 
     transl_transf[0,2] += anchorX 
    if minY < 0: 
     anchorY = np.round(-minY).astype(int) 
     transl_transf[1,2] += anchorY 
    new_transf = transl_transf.dot(transf) 
    new_transf /= new_transf[2,2] 

    dst_pad[anchorY:anchorY+dst_sz[0], anchorX:anchorX+dst_sz[1]] = dst 

    warped = cv2.warpPerspective(src, new_transf, (pad_sz[1],pad_sz[0])) 

    return dst_pad, warped 

例は、我々はいくつかの実際の画像とホモグラフィでこの関数を呼び出すと、それが出てパンどのように見ることができます。私はLearnOpenCVから例を借りましょう:

src = cv2.imread('book2.jpg') 
pts_src = np.array([[141, 131], [480, 159], [493, 630],[64, 601]], dtype=np.float32) 
dst = cv2.imread('book1.jpg') 
pts_dst = np.array([[318, 256],[534, 372],[316, 670],[73, 473]], dtype=np.float32) 

transf = cv2.getPerspectiveTransform(pts_src, pts_dst) 

dst_pad, warped = warpPerspectivePadded(src, dst, transf) 

alpha = 0.5 
beta = 1 - alpha 
blended = cv2.addWeighted(warped, alpha, dst_pad, beta, 1.0) 
cv2.imshow("Blended Warped Image", blended) 
cv2.waitKey(0) 

そして、私たちは、このパッド入りのワープ画像で終わる:

[Padded and warped[1]

あなたが正常になるだろうtypical cut off warpとは対照的です。

+1

うわー、そのような包括的かつ整然とした答えに感謝します。私は自分のやり方で働いていて、特定の概念について読んでいます。私が終わったら私の最終的な解決策を提示します。再度、感謝します! – Bprodz

関連する問題