Mathematica.Stackexchange पर एक समान प्रश्न पूछा गया था । वहाँ पर मेरा जवाब विकसित हुआ और अंत में काफी लंबा रहा, इसलिए मैं यहाँ एल्गोरिथम को संक्षेप में प्रस्तुत करूँगा।
सार
मूल विचार है:
- लेबल का पता लगाएं।
- लेबल की सीमाओं का पता लगाएं
- एक मानचित्रण खोजें जो मानचित्र छवि सिलेंडर के निर्देशांक से समन्वयित करता है ताकि यह पिक्सेल को लेबल की शीर्ष सीमा ([कुछ भी] / 0), पिक्सेल को सही सीमा के साथ (1 / [कुछ भी)) और इतने पर मैप कर सके।
- इस मैपिंग का उपयोग करके छवि को परिवर्तित करें
एल्गोरिथ्म केवल उन चित्रों के लिए काम करता है जहां:
- लेबल पृष्ठभूमि से उज्जवल है (यह लेबल पहचान के लिए आवश्यक है)
- लेबल आयताकार है (इसका उपयोग मैपिंग की गुणवत्ता को मापने के लिए किया जाता है)
- जार है (लगभग) वर्टिकल (इसका उपयोग मैपिंग फंक्शन को सरल रखने के लिए किया जाता है)
- जार बेलनाकार है (इसका उपयोग मैपिंग फ़ंक्शन को सरल रखने के लिए किया जाता है)
हालाँकि, एल्गोरिथ्म मॉड्यूलर है। कम से कम सिद्धांत रूप में, आप अपने स्वयं के लेबल का पता लगा सकते हैं जिसमें एक अंधेरे पृष्ठभूमि की आवश्यकता नहीं होती है, या आप अपने स्वयं के गुणवत्ता माप फ़ंक्शन लिख सकते हैं जो अण्डाकार या अष्टकोणीय लेबल के साथ सामना कर सकते हैं।
परिणाम
इन छवियों को पूरी तरह से स्वचालित रूप से संसाधित किया गया था, अर्थात एल्गोरिथ्म स्रोत छवि लेता है, कुछ सेकंड के लिए काम करता है, फिर मैपिंग (बाएं) और संयुक्त राष्ट्र-विकृत छवि (दाएं) दिखाता है:
अगली छवियों को एल्गोरिथ्म के एक संशोधित संस्करण के साथ संसाधित किया गया था, उपयोगकर्ता जार (लेबल नहीं) की बाईं और दाईं सीमाओं का चयन करते थे, क्योंकि लेबल की वक्रता का अनुमान ललाट शॉट (यानी) में छवि से नहीं लगाया जा सकता है पूरी तरह से स्वचालित एल्गोरिथ्म उन छवियों को वापस करेगा जो थोड़ा विकृत हैं):
कार्यान्वयन:
1. लेबल का पता लगाएं
लेबल एक काले रंग की पृष्ठभूमि के सामने उज्ज्वल है, इसलिए मैं इसे आसानी से बिनाराइजेशन का उपयोग करके पा सकता हूं:
src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]
मैं बस सबसे बड़ा जुड़ा घटक चुनता हूं और मान लेता हूं कि यह लेबल है:
labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]
2. लेबल की सीमाओं का पता लगाएं
अगला चरण: सरल व्युत्पन्न सजा मास्क का उपयोग करके ऊपर / नीचे / बाएँ / दाएँ सीमाएँ खोजें:
topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];
यह एक छोटा सहायक कार्य है जो इन चार छवियों में से एक में सभी सफेद पिक्सेल पाता है और सूचकांकों को निर्देशांक में परिवर्तित करता है (सूचकांकों को Position
इंगित करता है, और सूचकांक 1-आधारित {y, x} -tuples, जहां y = 1 शीर्ष पर है) छवि। लेकिन सभी इमेज प्रोसेसिंग फ़ंक्शंस समन्वय की उम्मीद करते हैं, जो 0-आधारित {x, y} -tuples, जहां y = 0 छवि के नीचे है):
{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];
3. छवि से सिलेंडर निर्देशांक के लिए एक मानचित्रण का पता लगाएं
अब मेरे पास लेबल के ऊपर, नीचे, बाएँ, दाएँ सीमाओं के निर्देशांक की चार अलग-अलग सूचियाँ हैं। मैं एक छवि मानचित्रण से निर्देशांक सिलेंडर निर्देशांक को परिभाषित करता हूं:
arcSinSeries = Normal[Series[ArcSin[\[Alpha]], {\[Alpha], 0, 10}]]
Clear[mapping];
mapping[{x_, y_}] :=
{
c1 + c2*(arcSinSeries /. \[Alpha] -> (x - cx)/r) + c3*y + c4*x*y,
top + y*height + tilt1*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]] + tilt2*y*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]]
}
यह एक बेलनाकार मानचित्रण है, जो स्रोत छवि में X / Y-निर्देशांक को बेलनाकार निर्देशांक में मैप करता है। मैपिंग में ऊंचाई / त्रिज्या / केंद्र / परिप्रेक्ष्य / झुकाव के लिए 10 डिग्री की स्वतंत्रता है। मैंने आर्क सीन्स को अनुमानित करने के लिए टेलर श्रृंखला का उपयोग किया, क्योंकि मुझे सीधे आर्किन के साथ काम करने का अनुकूलन नहीं मिला। Clip
कॉल अनुकूलन के दौरान जटिल संख्या को रोकने के लिए मेरा तदर्थ प्रयास है। यहां एक व्यापार बंद है: एक तरफ, फ़ंक्शन संभव के रूप में एक सटीक बेलनाकार मानचित्रण के करीब होना चाहिए, ताकि सबसे कम संभव विरूपण दिया जा सके। दूसरी ओर, यदि यह जटिल है, तो स्वत: स्वतंत्रता की डिग्री के लिए इष्टतम मान प्राप्त करना बहुत कठिन हो जाता है। (मैथेमेटिका के साथ इमेज प्रोसेसिंग करने के बारे में अच्छी बात यह है कि आप गणितीय मॉडलों के साथ इस तरह से बहुत आसानी से खेल सकते हैं, विभिन्न विकृतियों के लिए अतिरिक्त शर्तें पेश कर सकते हैं और अंतिम परिणामों को प्राप्त करने के लिए समान अनुकूलन कार्यों का उपयोग कर सकते हैं। मैं कभी भी कुछ नहीं कर पाया। OpenCV या Matlab का उपयोग करना पसंद है। लेकिन मैंने कभी मतलाब के लिए प्रतीकात्मक टूलबॉक्स की कोशिश नहीं की, हो सकता है कि यह इसे बहुत उपयोगी बनाता हो।)
अगला मैं एक "त्रुटि फ़ंक्शन" को परिभाषित करता हूं जो एक छवि की गुणवत्ता को मापता है -> सिलेंडर समन्वय मैपिंग। यह सीमा पिक्सल के लिए चुकता त्रुटियों का योग है:
errorFunction =
Flatten[{
(mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
(mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
(mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
(mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
}];
यह त्रुटि फ़ंक्शन मैपिंग की "गुणवत्ता" को मापता है: यह सबसे कम है यदि बाईं सीमा पर स्थित बिंदुओं (0 / [कुछ भी]) पर मैप किया जाता है, शीर्ष सीमा पर पिक्सल को ([कुछ भी] / 0) और इतने पर मैप किया जाता है ।
अब मैं गणितज्ञों को गुणांक खोजने के लिए कह सकता हूं जो इस त्रुटि फ़ंक्शन को कम करते हैं। मैं कुछ गुणांक के बारे में "शिक्षित अनुमान" बना सकता हूं (जैसे कि छवि में त्रिज्या और जार का केंद्र)। मैं अनुकूलन के शुरुआती बिंदुओं के रूप में इनका उपयोग करता हूं:
leftMean = Mean[maskToPoints[leftBorder]][[1]];
rightMean = Mean[maskToPoints[rightBorder]][[1]];
topMean = Mean[maskToPoints[topBorder]][[2]];
bottomMean = Mean[maskToPoints[bottomBorder]][[2]];
solution =
FindMinimum[
Total[errorFunction],
{{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0},
{cx, (leftMean + rightMean)/2},
{top, topMean},
{r, rightMean - leftMean},
{height, bottomMean - topMean},
{tilt1, 0}, {tilt2, 0}}][[2]]
FindMinimum
मेरी मैपिंग फ़ंक्शन की स्वतंत्रता की 10 डिग्री के लिए मान पाता है जो त्रुटि फ़ंक्शन को कम करता है। जेनेरिक मैपिंग और इस समाधान को मिलाएं और मुझे X / Y छवि निर्देशांक से एक मैपिंग मिलती है, जो लेबल क्षेत्र को फिट करती है। मैं गणितज्ञ के ContourPlot
कार्य का उपयोग करके इस मानचित्रण की कल्पना कर सकता हूं :
Show[src,
ContourPlot[mapping[{x, y}][[1]] /. solution, {x, 0, w}, {y, 0, h},
ContourShading -> None, ContourStyle -> Red,
Contours -> Range[0, 1, 0.1],
RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[2]] /. solution) <= 1]],
ContourPlot[mapping[{x, y}][[2]] /. solution, {x, 0, w}, {y, 0, h},
ContourShading -> None, ContourStyle -> Red,
Contours -> Range[0, 1, 0.2],
RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[1]] /. solution) <= 1]]]
4. छवि को बदलना
अंत में, मैं ImageForwardTransform
इस मैपिंग के अनुसार छवि को विकृत करने के लिए मैथेमेटिका के फ़ंक्शन का उपयोग करता हूं :
ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]
जैसा कि ऊपर दिखाया गया है कि परिणाम देता है।
मैन्युअल रूप से सहायता प्राप्त संस्करण
ऊपर एल्गोरिथ्म पूर्ण-स्वचालित है। कोई समायोजन की आवश्यकता है। यह यथोचित रूप से तब तक काम करता है जब तक कि तस्वीर ऊपर या नीचे से ली गई हो। लेकिन अगर यह एक ललाट शॉट है, तो जार की त्रिज्या का अनुमान लेबल के आकार से नहीं लगाया जा सकता है। इन मामलों में, मुझे बहुत बेहतर परिणाम मिलते हैं यदि मैं उपयोगकर्ता को जार की बाईं / दाईं सीमाओं को मैन्युअल रूप से दर्ज करने देता हूं, और मैपिंग में स्वतंत्रता की संबंधित डिग्री को स्पष्ट रूप से सेट करता हूं।
यह कोड उपयोगकर्ता को बाईं / दाईं सीमाओं का चयन करने देता है:
LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}],
Dynamic[Show[src,
Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}],
Line[{{xRight, 0}, {xRight, h}}]}]]]]
यह वैकल्पिक अनुकूलन कोड है, जहां केंद्र और त्रिज्या स्पष्ट रूप से दिए गए हैं।
manualAdjustments = {cx -> (xLeft + xRight)/2, r -> (xRight - xLeft)/2};
solution =
FindMinimum[
Total[minimize /. manualAdjustments],
{{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0},
{top, topMean},
{height, bottomMean - topMean},
{tilt1, 0}, {tilt2, 0}}][[2]]
solution = Join[solution, manualAdjustments]