opencv - WarpAffine का उपयोग किए बिना कटऑफ के साथ सिले हुए चित्रों को प्रदर्शित करना



image-stitching (1)

मैं टेम्पलेट मिलान का उपयोग करके 2 छवियों को एक साथ सिलाई करने की कोशिश कर रहा हूं 3 बिंदुओं के सेट खोजें जो मैं cv2.getAffineTransform() को पास करता cv2.getAffineTransform() एक ताना मैट्रिक्स मिलता है जिसे मैं अपनी छवियों को संरेखित करने के लिए cv2.warpAffine() में पास करता cv2.warpAffine()

हालाँकि जब मैं अपनी छवियों से जुड़ता हूँ तो मेरी बहुसंख्यक छवि नहीं दिखाई जाती है। मैंने विभिन्न तकनीकों का उपयोग करके पॉइंट चुनने, ऑर्डर या तर्कों को बदलने आदि की कोशिश की है, लेकिन मुझे केवल दिखाए जाने वाले एफिनेइन इमेज की पतली स्लेरो मिल सकती है।

क्या कोई मुझे बता सकता है कि क्या मेरा दृष्टिकोण एक वैध है और सुझाव दें कि मैं कहां त्रुटि कर सकता हूं? किसी भी अनुमान के रूप में क्या समस्या का कारण हो सकता है काफी सराहना की जाएगी। अग्रिम में धन्यवाद।

यह अंतिम परिणाम है जो मुझे मिलता है। यहां मूल चित्र ( 1 , 2 ) और मेरे द्वारा उपयोग किए जाने वाले कोड हैं:

संपादित करें: यहाँ चर trans के परिणाम हैं

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

और यहाँ cv2.getAffineTransform उत्तीर्ण अंक 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


12 जुलाई संपादित करें:

इस पोस्ट ने एक GitHub रेपो को प्रेरित किया जो इस कार्य को पूरा करने के लिए कार्य प्रदान करता है; एक गद्देदार warpAffine() और दूसरा एक गद्देदार warpPerspective() पायथन संस्करण या C ++ संस्करण को कांटा।

ट्रांसफ़ॉर्मेशन पिक्सेल के स्थान को स्थानांतरित कर देता है

कोई भी परिवर्तन आपके बिंदु निर्देशांक (x, y) को लेता है और उन्हें नए स्थानों (x', y') :

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

जहां s कुछ स्केलिंग कारक है। उचित पिक्सेल स्थानों (x', y') को वापस लाने के लिए आपको नए कारक को स्केल फैक्टर द्वारा विभाजित करना होगा। तकनीकी रूप से, यह केवल आत्मकथाओं का सच है --- (3, 3) ट्रांसफॉर्मेशन मैट्रिस --- आपको एफाइन ट्रांसफॉर्मेशन के लिए पैमाने की आवश्यकता नहीं है (आपको समरूप निर्देशांक का उपयोग करने की आवश्यकता नहीं है ... लेकिन इसे रखना बेहतर है यह चर्चा सामान्य)।

फिर वास्तविक पिक्सेल मूल्यों को उन नए स्थानों पर ले जाया जाता है, और नए पिक्सेल ग्रिड को फिट करने के लिए रंग मानों को प्रक्षेपित किया जाता है। इसलिए इस प्रक्रिया के दौरान, ये नए स्थान किसी बिंदु पर दर्ज हो जाते हैं। हमें उन स्थानों को देखने की आवश्यकता होगी जहाँ पिक्सेल वास्तव में दूसरी छवि के सापेक्ष चलते हैं। आइए एक आसान उदाहरण से शुरू करते हैं और देखते हैं कि अंकों को कहां मैप किया जाता है।

मान लीजिए कि आपका परिवर्तन मैट्रिक्स केवल पिक्सेल को बाईं ओर दस पिक्सेल से बदलता है। अनुवाद अंतिम कॉलम द्वारा संभाला जाता है; पहली पंक्ति x में अनुवाद है और दूसरी पंक्ति y में अनुवाद है। इसलिए हमारे पास एक पहचान मैट्रिक्स होगा, लेकिन -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) अंक प्राप्त करने होंगे, और उन्हें एक विशाल सरणी में रखना होगा ताकि हर एक बिंदु स्वयं के कॉलम में हो। हमारी छवि केवल 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.]]

जैसा कि हम देख सकते हैं, सब कुछ उम्मीद के मुताबिक काम कर रहा है: हमने x -values ​​को केवल -10 स्थानांतरित कर दिया है।

पिक्सेल को आपकी छवि सीमा के बाहर स्थानांतरित किया जा सकता है

ध्यान दें कि ये पिक्सेल स्थान नकारात्मक हैं --- वे छवि सीमा के बाहर हैं। यदि हम कुछ अधिक जटिल करते हैं और छवि को 45 डिग्री से घुमाते हैं, तो हमें अपने मूल सीमा के बाहर कुछ पिक्सेल मान मिलेंगे। हम हर पिक्सेल मूल्य के बारे में परवाह नहीं करते हैं, हमें बस यह जानने की जरूरत है कि मूल छवि पिक्सेल स्थानों के बाहर कितनी दूर पिक्सेल हैं, ताकि हम उस पर विकृत छवि को प्रदर्शित करने से पहले उस मूल छवि को दूर कर सकें। ।

theta = 45*np.pi/180
transf = np.array([
    [ np.cos(theta),np.sin(theta),0],
    [-np.sin(theta),np.cos(theta),0],
    [0.,0.,1.]])
print(transf)
trans_lin_homg_pts = transf.dot(lin_homg_pts)
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,:])
# minX: 0.0, minY: -2.12132034356, maxX: 4.24264068712, maxY: 2.12132034356,

इसलिए हम देखते हैं कि हम नकारात्मक और सकारात्मक दोनों दिशाओं में अपनी मूल छवि के बाहर पिक्सेल स्थानों को अच्छी तरह से प्राप्त कर सकते हैं। न्यूनतम x मान नहीं बदलता है क्योंकि जब कोई होमोग्राफी रोटेशन को लागू करता है, तो वह शीर्ष-बाएं कोने से करता है। अब यहां एक बात ध्यान देने वाली है कि मैंने इमेज में सभी पिक्सल के लिए ट्रांसफॉर्मेशन लागू कर दिया है। लेकिन यह वास्तव में अनावश्यक है, आप बस चार कोने बिंदुओं को ताना और यह देख सकते हैं कि वे कहाँ उतरते हैं।

डेस्टिनेशन इमेज को पेड करना

ध्यान दें कि जब आप cv2.warpAffine() हैं, तो आपको गंतव्य आकार इनपुट करना होगा। ये रूपांतरित पिक्सेल मान उस आकार को संदर्भित करते हैं। इसलिए यदि कोई पिक्सेल (-10,0) मैप किया जाता है, तो वह गंतव्य छवि में दिखाई नहीं देगा। इसका मतलब है कि हमें उन अनुवादों के साथ एक और होमोग्राफी करनी होगी जो सभी पिक्सेल स्थानों को सकारात्मक बनाते हैं और फिर हम अपनी पारी की भरपाई के लिए छवि मैट्रिक्स को पैड कर सकते हैं। अगर होमोग्राफी छवि की तुलना में बड़े पदों की ओर इशारा करती है, तो हमें मूल छवि को नीचे और दाईं ओर रखना होगा।

हाल के उदाहरण में, न्यूनतम x मान समान है, इसलिए हमें कोई क्षैतिज पारी नहीं चाहिए। हालाँकि, न्यूनतम y मान में लगभग दो पिक्सेल की गिरावट आई है, इसलिए हमें छवि को दो पिक्सेल नीचे स्थानांतरित करने की आवश्यकता है। सबसे पहले, गद्देदार गंतव्य छवि बनाते हैं।

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]

जैसा कि हम देख सकते हैं, उस पारी के लिए खाते की ऊंचाई मूल से दो पिक्सेल बढ़ गई।

सभी पिक्सेल स्थानों को सकारात्मक में बदलने के लिए परिवर्तन में अनुवाद जोड़ें

अब, हमें उसी राशि से विकृत छवि का अनुवाद करने के लिए एक नई होमोग्राफ़ी मैट्रिक्स बनाने की आवश्यकता है जिसे हमने स्थानांतरित किया था। और दोनों परिवर्तनों को लागू करने के लिए --- मूल और यह नई पारी --- हमें दो गृहस्थों की रचना करनी है (एक परिणीति परिवर्तन के लिए, आप केवल अनुवाद जोड़ सकते हैं, लेकिन एक होमोग्राफी के लिए नहीं)। इसके अतिरिक्त हमें यह सुनिश्चित करने के लिए अंतिम प्रविष्टि से विभाजित करने की आवश्यकता है कि तराजू अभी भी उचित है (फिर से, केवल होमोग्राफ के लिए):

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]))

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

यह सब एक साथ डालें

चलिए इसके लिए एक फंक्शन बनाते हैं क्योंकि हम कुछ वैरिएबल बना रहे थे जिसकी हमें यहाँ पर कोई ज़रूरत नहीं है। इनपुट्स के लिए हमें सोर्स इमेज, डेस्टिनेशन इमेज और ओरिजिनल होमोग्राफी की जरूरत होती है। और आउटपुट के लिए हम केवल गद्देदार गंतव्य छवि, और विकृत छवि चाहते हैं। ध्यान दें कि हमने एक 3x3 होमोग्राफी का उपयोग किया था, इसलिए हम यह सुनिश्चित करते हैं कि हम 2x3 एफाइन या यूक्लिडियन वॉर के बजाय 3x3 ट्रांसफ़ॉर्म में भेजें। आप केवल पंक्ति [0,0,1] को नीचे के किसी भी [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 से उदाहरण उधार 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)

और हम इस गद्देदार छवि के साथ अंत करते हैं:

के रूप में आप आमतौर पर मिलेगा ताना काट बंद का विरोध किया।





image-stitching