python التباين التلقائي وضبط السطوع لصورة ملونة من ورقة مع OpenCV




image-processing computer-vision (4)

قوي ثنائي التكيف لينة محليا! هذا ما أسميه.

لقد فعلت أشياء مماثلة من قبل ، ولأغراض مختلفة بعض الشيء ، لذلك قد لا يناسب هذا احتياجاتك تمامًا ، ولكن آمل أن يساعدها (كما كتبت هذا الرمز في الليل للاستخدام الشخصي ، لذا فهو قبيح). بمعنى ما ، كان الهدف من هذا الرمز هو حل حالة أكثر عمومية مقارنة بحالتك ، حيث يمكن أن يكون لدينا الكثير من الضوضاء المنظمة في الخلفية (انظر العرض التوضيحي أدناه).

ماذا يفعل هذا الرمز؟ عند إعطاء صورة لورقة من الورق ، سيتم تبييضها بحيث تكون قابلة للطباعة تمامًا. انظر المثال الصور أدناه.

دعابة: هذه هي الطريقة التي ستبدو بها صفحاتك بعد هذه الخوارزمية (قبل وبعد). لاحظ أنه حتى التعليقات التوضيحية لعلامة اللون قد ولت ، لذلك لا أعرف ما إذا كان هذا سيتناسب مع حالة الاستخدام ولكن قد يكون الرمز مفيدًا:

للحصول على نتائج نظيفة تمامًا ، قد تحتاج إلى التجوّل مع معلمات التصفية قليلاً ، ولكن كما ترون ، حتى مع المعلمات الافتراضية ، فهي تعمل جيدًا.

الخطوة 0: قص الصور لتناسب الصفحة عن كثب

لنفترض أنك قمت بهذه الخطوة بطريقة أو بأخرى (يبدو ذلك في الأمثلة التي قدمتها). إذا كنت بحاجة إلى أداة التعليق التوضيحي اليدوي وإعادة تعمير ، فقط مساءً! ^ ^ نتائج هذه الخطوة أدناه (يمكن القول إن الأمثلة التي أستخدمها هنا أصعب من تلك التي قدمتها ، في حين أنها قد لا تتطابق تمامًا مع قضيتك):

من هذا يمكننا أن نرى على الفور المشاكل التالية:

  • حالة البرق ليست حتى. هذا يعني أن جميع أساليب الترميز الثنائي البسيط لن تعمل. جربت الكثير من الحلول المتاحة في 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 كبيرًا بما يكفي لجعل الغالبية العظمى منها خلفية). بعد ذلك ، نقوم بتعريف DELTA - بشكل أساسي مجرد عتبة "إلى أي مدى سنظل نعتبرها خلفية؟".

لذلك ، تقوم الدالة process_image المهمة. علاوة على ذلك ، يمكنك تعديل دالات preprocess و postprocess لتناسب احتياجاتك (ومع ذلك ، كما ترى من المثال أعلاه ، فإن الخوارزمية قوية جدًا ، أي أنها تعمل بشكل جيد للغاية دون تعديل الكثير من المعلمات ).

يفترض رمز هذا الجزء أن يكون المقدمة أكثر قتامة من الخلفية (أي الحبر على الورق). لكن يمكنك بسهولة تغيير ذلك عن طريق تعديل وظيفة preprocess : بدلاً من 255 - image ، قم بإرجاع image فقط.

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

النتائج رائعة مثل هذه ، تتبع عن كثب أثر الحبر:

الخطوة 3: الجزء "اللين" من الترميز

وجود النقط التي تغطي الرموز وأكثر من ذلك بقليل ، يمكننا أخيرًا إجراء عملية التبييض.

إذا نظرنا عن كثب إلى صور أوراق الأوراق التي تحتوي على نص (خاصة تلك التي تحتوي على كتابات يدوية) ، فإن التحول من "الخلفية" (الورقة البيضاء) إلى "المقدمة" (حبر الألوان الداكنة) ليس حادًا ، لكنه تدريجي جدًا . تقترح الإجابات الأخرى القائمة على الترميز الثنائي في هذا القسم عتبة بسيطة (حتى لو كانت قابلة للتكيف محليًا ، فهي لا تزال عتبة) ، والتي تعمل بشكل جيد بالنسبة للنص المطبوع ، ولكنها ستنتج نتائج غير جميلة بالكتابات اليدوية.

لذا ، فإن الدافع وراء هذا القسم هو أننا نريد الحفاظ على هذا التأثير للانتقال التدريجي من الأسود إلى الأبيض ، تمامًا مثل الصور الطبيعية لأوراق الأوراق بالحبر الطبيعي. الغرض النهائي من ذلك هو جعله قابلاً للطباعة.

الفكرة الرئيسية بسيطة: فكلما كانت قيمة البيكسل (بعد العتبة أعلاه) تختلف عن قيمة الدقائق المحلية ، زاد احتمال كونها تنتمي إلى الخلفية. يمكننا التعبير عن ذلك باستخدام مجموعة من وظائف Sigmoid ، وإعادة تحجيمها إلى نطاق الكتلة المحلية (بحيث يتم تحجيم هذه الوظيفة على نحو شامل للصورة).

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

تم التعليق على بعض المواد لأنها اختيارية. تأخذ الدالة combine_process القناع من الخطوة السابقة ، وتقوم بتنفيذ خط أنابيب التكوين بالكامل. يمكنك محاولة اللعب معهم لبياناتك المحددة (الصور). النتائج نظيفة:

ربما سأضيف المزيد من التعليقات والتفسيرات إلى الكود في هذه الإجابة. سيتم تحميل كل شيء (مع رمز الاقتصاص والتزييف) على جيثب.

عند تصوير ورقة (على سبيل المثال مع كاميرا الهاتف) ، أحصل على النتيجة التالية (الصورة اليسرى) (تنزيل jpg here ). النتيجة المرجوة (تتم معالجتها يدويًا باستخدام برنامج لتحرير الصور) على اليمين:

أرغب في معالجة الصورة الأصلية باستخدام openCV للحصول على سطوع / تباين أفضل تلقائيًا (بحيث تكون الخلفية بيضاء أكثر) .

الافتراض: تحتوي الصورة على تنسيق صورة A4 (لا نحتاج إلى تشويهها في المنظور في هذا الموضوع هنا) ، وورقة الورق بيضاء اللون مع وجود نص / صور باللون الأسود أو الألوان.

ما جربته حتى الآن:

  1. طرق العتبة التكيفية المختلفة مثل Gaussian و OTSU (انظر OpenCV doc Image Thresholding ). عادة ما يعمل بشكل جيد مع OTSU:

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

    لكنه يعمل فقط مع الصور ذات درجات الرمادي وليس مباشرة للصور الملونة. علاوة على ذلك ، يكون الإخراج ثنائيًا (أبيض أو أسود) ، وهو ما لا أريده : أفضل الاحتفاظ صورة ملونة غير ثنائية كمخرجات

  2. الرسم البياني معادلة

    • تطبق على Y (بعد RGB => تحويل YUV)
    • أو تطبيقها على V (بعد RGB => تحويل HSV) ،

    كما هو مقترح في هذه answer ( لا تعمل معادلة الرسم البياني على الصورة الملونة - OpenCV ) أو هذه (صورة OpenCV Python equalizeHist الملونة ):

    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)

    أو مع HSV:

    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. كما جربت CLAHE (التباين المحدود للتوازن المدرج التكراري) مع tileGridSize مختلفة من 1 إلى 1000 :

    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. القيام بطريقة CLAHE هذه باستخدام مساحة ألوان LAB ، كما هو مقترح في السؤال كيفية تطبيق CLAHE على الصور الملونة RGB :

    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 .

  6. طريقة "التباين strechting" من البرنامج التعليمي scikit-image على معادلة الرسم البياني :

    يتم تغيير حجم الصورة لتشمل جميع الشدة التي تقع ضمن النسب المئوية الثانية والتاسعة والتسعين

    أفضل قليلاً ، لكن لا تزال بعيدة عن النتيجة المرجوة (انظر الصورة في أعلى هذا السؤال).

TL ؛ DR: كيفية الحصول على تحسين سطوع / تباين تلقائي لصورة ملونة لورقة من الورق باستخدام OpenCV / Python؟ أي نوع من معادلة العتبة / الرسم البياني / تقنية أخرى يمكن استخدامها؟


أولاً نفصل بين علامات النص واللون. يمكن القيام بذلك في فراغ اللون باستخدام قناة تشبع اللون. لقد استخدمت بدلاً من ذلك طريقة بسيطة للغاية مستوحاة من هذه الورقة : ستكون حصة min (R ، G ، B) / max (R ، G ، B) بالقرب من 1 للمساحات الرمادية (الفاتحة) و << 1 للمناطق الملونة. بالنسبة للمساحات الرمادية الداكنة ، نحصل على أي شيء يتراوح بين 0 و 1 ، لكن هذا لا يهم: إما أن تذهب هذه المساحات إلى قناع اللون ثم تضاف كما هي أو لا يتم تضمينها في القناع وتساهم في الإخراج من bin Binated نص. للأسود نستخدم حقيقة أن 0/0 تصبح 0 عند التحويل إلى uint8.

يحصل عتبة نص الصورة ذات التدرج الرمادي محلياً لإنتاج صورة بالأبيض والأسود. يمكنك اختيار الأسلوب المفضل لديك من هذه المقارنة أو تلك الدراسة . لقد اخترت تقنية NICK التي تتواءم بشكل جيد مع تباين منخفض وقوية إلى حد ما ، أي أن اختيار المعلمة k بين حوالي -0.3 و -0.1 يعمل بشكل جيد لمجموعة واسعة جدًا من الشروط التي تعد جيدة للمعالجة التلقائية. بالنسبة إلى نموذج المستند المقدم ، لا تلعب التقنية المختارة دورًا كبيرًا لأنها مضاءة بشكل موحد نسبيًا ، ولكن من أجل التعامل مع الصور غير المضاءة بشكل موحد ، يجب أن تكون تقنية حد محلية .

في الخطوة الأخيرة ، تتم إضافة مساحات الألوان مرة أخرى إلى الصورة النصية ثنائية الاتجاه.

هذا الحل يشبه إلى حد كبير حل @ 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) 


يجب أن تعمل هذه الطريقة بشكل جيد للتطبيق الخاص بك. أولاً ، يمكنك العثور على قيمة عتبة تفصل أوضاع التوزيع جيدًا في الرسم البياني للشدة ثم تعيد قياس الكثافة باستخدام تلك القيمة.

 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) 

أنا هنا باستخدام طريقة الين ، ويمكن معرفة المزيد حول هذه الطريقة على هذه الصفحة .


يمكن ضبط السطوع والتباين باستخدام alpha (α) و beta (β) ، على التوالي. يمكن كتابة التعبير باسم

يطبق OpenCV هذا بالفعل كـ cv2.convertScaleAbs() حتى نتمكن من استخدام هذه الوظيفة مع قيم alpha beta المعرفة من قبل المستخدم.

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

لكن السؤال كان

كيفية الحصول على تحسين سطوع / تباين تلقائي لصورة ملونة؟

السؤال الأساسي هو كيفية حساب alpha beta تلقائيًا. للقيام بذلك ، يمكننا أن ننظر إلى الرسم البياني للصورة. يقوم السطوع التلقائي وتحسين التباين بحساب 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٪







image-thresholding