python - OpenCV के साथ कागज की एक शीट के रंगीन फोटो का स्वत: विपरीत और चमक समायोजन




image-processing computer-vision (4)

मजबूत स्थानीय-अनुकूल बाइनराइजेशन! इसे ही मैं कहता हूं।

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

यह कोड क्या करता है? कागज की एक शीट की एक तस्वीर को देखते हुए, यह इसे सफेद कर देगा ताकि यह पूरी तरह से प्रिंट करने योग्य हो सके। नीचे उदाहरण चित्र देखें।

टीज़र: कि इस एल्गोरिथम (पहले और बाद) के बाद आपके पृष्ठ कैसे दिखेंगे। ध्यान दें कि रंग मार्कर एनोटेशन भी चले गए हैं, इसलिए मुझे नहीं पता कि यह आपके उपयोग के मामले में फिट होगा या नहीं, लेकिन कोड उपयोगी हो सकता है:

पूरी तरह से साफ परिणाम प्राप्त करने के लिए, आपको फ़िल्टरिंग मापदंडों के साथ आस-पास खिलौने की आवश्यकता हो सकती है, लेकिन जैसा कि आप देख सकते हैं, डिफ़ॉल्ट मापदंडों के साथ भी यह काफी अच्छी तरह से काम करता है।

चरण ०: पेज के करीब आने के लिए छवियों को काटें

चलो मान लें कि आपने किसी तरह यह कदम उठाया (ऐसा लगता है कि आपके द्वारा दिए गए उदाहरणों में)। यदि आपको एक मैनुअल एनोटेट-एंड-रवेरप टूल की आवश्यकता है, तो बस मुझे दोपहर! ^ ^ इस चरण के परिणाम नीचे हैं (मेरे द्वारा उपयोग किए गए उदाहरण आपके द्वारा प्रदान किए गए यकीनन कठिन हैं, जबकि यह आपके मामले से बिल्कुल मेल नहीं खा सकता है):

इससे हम निम्नलिखित समस्याओं को तुरंत देख सकते हैं:

  • बिजली की हालत भी ठीक नहीं है। इसका मतलब यह है कि सभी सरल द्विभाजन विधि काम नहीं करेंगी। मैंने OpenCV में उपलब्ध समाधानों की बहुत कोशिश की, साथ ही साथ उनके संयोजन, उनमें से किसी ने भी काम नहीं किया!
  • बहुत सारा बैकग्राउंड शोर। मेरे मामले में, मुझे कागज के ग्रिड को हटाने की आवश्यकता थी, और कागज के दूसरी तरफ से स्याही भी जो पतली शीट के माध्यम से दिखाई देती है।

चरण 1: गामा सुधार

इस कदम का तर्क पूरी छवि के विपरीत को संतुलित करना है (चूंकि आपकी छवि प्रकाश की स्थिति के आधार पर थोड़ी अधिक खराब हो सकती है / पूर्ववत हो सकती है)।

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

from skimage.filters import threshold_yen
from skimage.exposure import rescale_intensity
from skimage.io import imread, imsave

img = imread('mY7ep.jpg')

yen_threshold = threshold_yen(img)
bright = rescale_intensity(img, (0, yen_threshold), (0, 255))

imsave('out.jpg', bright)

यहाँ गामा समायोजन के परिणाम हैं:

आप देख सकते हैं कि यह थोड़ा अधिक है ... "संतुलित" अब। इस चरण के बिना, बाद में चरणों में आपके द्वारा चुने जाने वाले सभी पैरामीटर कम मजबूत हो जाएंगे!

चरण 2: टेक्स्ट बलेब्स का पता लगाने के लिए अनुकूली द्वैरीकरण

इस चरण में, हम टेक्स्ट ब्लब्स को अनुकूल रूप से बाहर कर देंगे। मैं बाद में और टिप्पणियां जोड़ूंगा, लेकिन मूल रूप से यह विचार निम्नलिखित है:

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

तो, function_image कार्य process_image हो जाता है। इसके अलावा, आप अपनी आवश्यकता को फिट करने के लिए preprocess और postprocess कार्यों को संशोधित कर सकते हैं (हालांकि, जैसा कि आप ऊपर दिए गए उदाहरण से देख सकते हैं, एल्गोरिथ्म बहुत मजबूत है , अर्थात यह बहुत अधिक आउट-ऑफ-बॉक्स काम करता है बिना बहुत अधिक मापदंडों को संशोधित किए। )।

इस भाग का कोड पृष्ठभूमि (अर्थात कागज पर स्याही) की तुलना में अग्रभूमि को गहरा मानता है। लेकिन आप आसानी से preprocess फंक्शन को preprocess करके बदल सकते हैं: 255 - image बजाय 255 - image , सिर्फ image वापस image

magick image.jpg -colorspace HCL -channel 1 -separate +channel tmp1.png

परिणाम इस तरह से अच्छे हैं, स्याही का पता लगाने के बाद बारीकी से:

चरण 3: बिनराइजेशन का "सॉफ्ट" हिस्सा

उन बिम्बों का होना जो प्रतीकों को ढंकता है और थोड़ा और अधिक, हम अंततः श्वेत करने की प्रक्रिया कर सकते हैं।

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

तो, इस खंड की प्रेरणा यह है कि हम काले और सफेद से क्रमिक संचरण के उस प्रभाव को संरक्षित करना चाहते हैं, जिस तरह प्राकृतिक स्याही से कागज की चादरों की प्राकृतिक तस्वीरें। इसका अंतिम उद्देश्य इसे प्रिंट करने योग्य बनाना है

मुख्य विचार सरल है: अधिक पिक्सेल मूल्य (ऊपर थ्रेसहोल्ड के बाद) स्थानीय न्यूनतम मूल्य से भिन्न होता है, अधिक संभावना यह पृष्ठभूमि से संबंधित है। हम सिग्मॉइड फ़ंक्शंस के एक परिवार का उपयोग करके इसे व्यक्त कर सकते हैं, स्थानीय ब्लॉक की सीमा तक फिर से स्केल किया गया है (ताकि यह फ़ंक्शन अनुकूल रूप से छवि को पूरी तरह से बढ़ाया जाए)।

magick tmp1.png -auto-threshold otsu tmp2.png

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

संभवतः मैं इस उत्तर में कोड में अधिक टिप्पणियां और स्पष्टीकरण जोड़ूंगा। जीथब पर पूरी बात (एक साथ फसल और वारपिंग कोड) अपलोड करेंगे।

जब कागज की एक शीट (जैसे फोन कैमरा के साथ) फोटो खींचते हैं, तो मुझे निम्न परिणाम (बाईं छवि) (jpg here डाउनलोड here ) मिलता है। वांछित परिणाम (छवि संपादन सॉफ्टवेयर के साथ मैन्युअल रूप से संसाधित) दाईं ओर है:

मैं ओरिजिनल इमेज को ओपन सीवी के साथ प्रोसेस करना चाहता हूं ताकि अपने आप एक बेहतर ब्राइटनेस / कॉन्ट्रास्ट मिल सके (ताकि बैकग्राउंड ज्यादा सफेद हो)

धारणा: छवि में A4 चित्र प्रारूप है (हमें इस विषय में इसे परिप्रेक्ष्य-ताना-बाना करने की आवश्यकता नहीं है), और कागज की शीट संभवतः काले / रंगों में पाठ / छवियों के साथ सफेद है।

मैंने अब तक क्या प्रयास किया है:

  1. गॉसियन, ओटीएसयू (ओपनसीवी डॉक्टर छवि थ्रेसहोल्ड देखें ) जैसे विभिन्न अनुकूली थ्रेसहोल्ड तरीके। यह आमतौर पर OTSU के साथ अच्छी तरह से काम करता है:

    ret, gray = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)

    लेकिन यह केवल ग्रेस्केल छवियों के लिए काम करता है और सीधे रंग छवियों के लिए नहीं। इसके अलावा, आउटपुट बाइनरी (सफेद या काला) है, जो मुझे नहीं चाहिए : मैं आउटपुट के रूप में एक गैर-बाइनरी छवि रखना पसंद करता हूं

  2. हिस्टोग्राम समीकरण

    • Y (RGB => YUV परिवर्तन के बाद) पर लागू
    • या वी (आरजीबी => एचएसवी परिवर्तन के बाद) पर लागू होता है,

    जैसा कि इस answer चलता है ( हिस्टोग्राम समीकरण रंग छवि पर काम नहीं कर रहा है - OpenCV ) या यह one ( OpenCV पायथन बराबर रंगीन छवि ):

    img3 = cv2.imread(f)
    img_transf = cv2.cvtColor(img3, cv2.COLOR_BGR2YUV)
    img_transf[:,:,0] = cv2.equalizeHist(img_transf[:,:,0])
    img4 = cv2.cvtColor(img_transf, cv2.COLOR_YUV2BGR)
    cv2.imwrite('test.jpg', img4)

    या एचएसवी के साथ:

    img_transf = cv2.cvtColor(img3, cv2.COLOR_BGR2HSV)
    img_transf[:,:,2] = cv2.equalizeHist(img_transf[:,:,2])
    img4 = cv2.cvtColor(img_transf, cv2.COLOR_HSV2BGR)

    दुर्भाग्य से, परिणाम काफी बुरा है क्योंकि यह स्थानीय रूप से भयानक माइक्रो विरोधाभास बनाता है (?):

    मैंने इसके बजाय YCbCr की कोशिश की, और यह समान था।

  3. मैंने 1 से 1000 तक विभिन्न tileGridSize साथ CLAHE (कंट्रास्ट लिमिटेड एडेप्टिव हिस्टोग्राम tileGridSize ) की भी कोशिश की:

    img3 = cv2.imread(f)
    img_transf = cv2.cvtColor(img3, cv2.COLOR_BGR2HSV)
    clahe = cv2.createCLAHE(tileGridSize=(100,100))
    img_transf[:,:,2] = clahe.apply(img_transf[:,:,2])
    img4 = cv2.cvtColor(img_transf, cv2.COLOR_HSV2BGR)
    cv2.imwrite('test.jpg', img4)

    लेकिन परिणाम भी उतना ही भयानक था।

  4. इस रंग विधि को LAB रंग स्थान के साथ करना, जैसा कि इस सवाल में सुझाया गया है कि RGB रंग छवियों पर CLAHE कैसे लागू करें :

    import cv2, numpy as np
    bgr = cv2.imread('_example.jpg')
    lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
    lab_planes = cv2.split(lab)
    clahe = cv2.createCLAHE(clipLimit=2.0,tileGridSize=(100,100))
    lab_planes[0] = clahe.apply(lab_planes[0])
    lab = cv2.merge(lab_planes)
    bgr = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
    cv2.imwrite('_example111.jpg', bgr)

    बुरा परिणाम भी दिया। आउटपुट छवि:

  5. प्रत्येक चैनल (R, G, B) पर अलग से एक अनुकूली थ्रेशोल्डिंग या हिस्टोग्राम समीकरण करें क्योंकि यह रंग संतुलन के साथ खिलवाड़ नहीं करेगा, जैसा कि here बताया गया here

  6. हिस्टोग्राम scikit-image पर scikit-image के ट्यूटोरियल से "कंट्रास्ट स्ट्रेचिंग" विधि:

    छवि 2 और 98 प्रतिशत प्रतिशत के भीतर आने वाली सभी तीव्रता को शामिल करने के लिए बदल दी गई है

    थोड़ा बेहतर है, लेकिन अभी भी वांछित परिणाम से दूर है (इस प्रश्न के शीर्ष पर छवि देखें)।

टीएल; डीआर: ओपनसीवी / पायथन के साथ कागज की शीट के रंगीन फोटो का एक स्वचालित चमक / कंट्रास्ट अनुकूलन कैसे प्राप्त करें? किस प्रकार की थ्रेसहोल्डिंग / हिस्टोग्राम इक्विलाइजेशन / अन्य तकनीक का उपयोग किया जा सकता है?


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

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

अंतिम चरण में, रंग क्षेत्रों को बिनाराइज्ड टेक्स्ट छवि में वापस जोड़ा जाता है।

तो यह समाधान @ fmw42 के समाधान (उस पर विचार के लिए सभी क्रेडिट) के समान है, जो अलग-अलग रंग का पता लगाने और द्वैरीकरण विधियों के अपवाद के साथ है।

 image = cv2.imread('mY7ep.jpg') # make mask and inverted mask for colored areas b,g,r = cv2.split(cv2.blur(image,(5,5))) np.seterr(divide='ignore', invalid='ignore') # 0/0 --> 0 m = (np.fmin(np.fmin(b, g), r) / np.fmax(np.fmax(b, g), r)) * 255 _,mask_inv = cv2.threshold(np.uint8(m), 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) mask = cv2.bitwise_not(mask_inv) # local thresholding of grayscale image gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) text = cv2.ximgproc.niBlackThreshold(gray, 255, cv2.THRESH_BINARY, 41, -0.1, binarizationMethod=cv2.ximgproc.BINARIZATION_NICK) # create background (text) and foreground (color markings) bg = cv2.bitwise_and(text, text, mask = mask_inv) fg = cv2.bitwise_and(image, image, mask = mask) out = cv2.add(cv2.cvtColor(bg, cv2.COLOR_GRAY2BGR), fg) 

यदि आपको रंग चिह्नों की आवश्यकता नहीं है, तो आप बस ग्रेस्केल छवि को बिनाराइज कर सकते हैं:

 image = cv2.imread('mY7ep.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) text = cv2.ximgproc.niBlackThreshold(gray, 255, cv2.THRESH_BINARY, at_bs, -0.3, binarizationMethod=cv2.ximgproc.BINARIZATION_NICK) 


चमक और कंट्रास्ट को क्रमशः अल्फा (α) और बीटा (,) का उपयोग करके समायोजित किया जा सकता है। अभिव्यक्ति के रूप में लिखा जा सकता है

OpenCV पहले से ही इसे cv2.convertScaleAbs() रूप में लागू करता है, इसलिए हम केवल उपयोगकर्ता द्वारा परिभाषित alpha और beta मानों के साथ इस फ़ंक्शन का उपयोग कर सकते हैं।

magick image.jpg -colorspace gray -negate -lat 20x20+10% -negate tmp3.png

लेकिन सवाल था

कलर फोटो के ऑटोमैटिक ब्राइटनेस / कंट्रास्ट ऑप्टिमाइज़ेशन कैसे प्राप्त करें?

अनिवार्य रूप से सवाल यह है कि alpha और beta स्वचालित रूप से गणना कैसे की जाए। ऐसा करने के लिए, हम छवि के हिस्टोग्राम को देख सकते हैं। स्वचालित चमक और कंट्रास्ट ऑप्टिमाइज़ेशन अल्फा और बीटा की गणना करता है ताकि आउटपुट रेंज [0...255] । हम यह निर्धारित करने के लिए संचयी वितरण की गणना करते हैं कि रंग आवृत्ति कुछ थ्रेशोल्ड मान (1%) से कम है और हिस्टोग्राम के दाएं और बाएं पक्षों को काटें। यह हमें हमारी न्यूनतम और अधिकतम सीमाएँ प्रदान करता है। यहाँ (नीला) से पहले और क्लिपिंग (नारंगी) के बाद हिस्टोग्राम का एक दृश्य है।

alpha गणना करने के लिए, हम कतरन के बाद न्यूनतम और अधिकतम ग्रेस्केल रेंज लेते हैं और इसे हमारे वांछित आउटपुट रेंज 255 से विभाजित करते हैं

magick tmp3.png \( image.jpg tmp2.png -alpha off -compose copy_opacity -composite \) -compose over -composite result.png

बीटा की गणना करने के लिए, हम इसे सूत्र में प्लग करते हैं जहाँ g(i, j)=0 और f(i, j)=minimum_gray

import cv2
import numpy as np
from matplotlib import pyplot as plt

image = cv2.imread('1.jpg')

alpha = 1.95 # Contrast control (1.0-3.0)
beta = 0 # Brightness control (0-100)

manual_result = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)

cv2.imshow('original', image)
cv2.imshow('manual_result', manual_result)
cv2.waitKey()

जो इस में परिणाम को हल करने के बाद

α = 255 / (maximum_gray - minimum_gray)

आपकी छवि के लिए हमें यह मिलता है

अल्फा 3.75

बीटा -311.25

आपको परिणामों को परिष्कृत करने के लिए कतरन सीमा मूल्य समायोजित करना पड़ सकता है। अन्य छवियों के साथ 1% थ्रेशोल्ड का उपयोग करके कुछ उदाहरण परिणाम यहां दिए गए हैं

स्वचालित चमक और कंट्रास्ट कोड

g(i,j) = α * f(i,j) + β

इस कोड के साथ परिणाम छवि:

1% सीमा का उपयोग करते हुए अन्य छवियों के साथ परिणाम


यह विधि आपके आवेदन के लिए अच्छी तरह से काम करना चाहिए। पहले आप एक थ्रेसहोल्ड मान पाते हैं जो वितरण मोड को तीव्रता हिस्टोग्राम में अच्छी तरह से अलग करता है फिर उस मूल्य का उपयोग करके तीव्रता को फिर से खोजें।

 from skimage.filters import threshold_yen from skimage.exposure import rescale_intensity from skimage.io import imread, imsave img = imread('mY7ep.jpg') yen_threshold = threshold_yen(img) bright = rescale_intensity(img, (0, yen_threshold), (0, 255)) imsave('out.jpg', bright) 

मैं येन की विधि का उपयोग कर रहा हूं, इस पृष्ठ पर इस पद्धति के बारे में अधिक जान सकता हूं।





image-thresholding