2025, Oct 17 19:34

काले‑सफेद इमेज में बाहरी सीमा अलग करना: OpenCV कनेक्टेड कंपोनेंट्स (बिना इनपेंटिंग)

काले‑सफेद ग्राफिक्स में बाहरी सीमा को अंदरूनी आकृतियों से अलग करने की भरोसेमंद OpenCV विधि: कनेक्टेड कंपोनेंट्स; बिना इनपेंटिंग आर्टिफैक्ट, स्ट्रोक मोटाई सुरक्षित।

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

सीधी‑सादी विधि कहाँ चूकती है

सहज पहला कदम सबसे बड़ा बाहरी कंटूर ढूंढकर उसे हटा देना होता है। फ्रेम के मामले में विचार ठीक लगता है, लेकिन बाहरी आकृति आयताकार न होते ही यह तरीका अटकने लगता है और इनपेंटिंग के धब्बे छोड़ देता है। नीचे उसी दृष्टि का एक संक्षिप्त उदाहरण है:

import cv2
import numpy as np

def strip_frame_only(input_file,
                     frame_px=20,
                     patch_r=3):
    """
    Remove only the outer frame lines of thickness `frame_px`,
    preserving interior content.

    Args:
        input_file (str): Path to the input image.
        frame_px (int): Approximate pixel thickness of the outer border.
        patch_r (int): Radius for the inpainting algorithm.

    Returns:
        np.ndarray: Image with only the border removed.
    """
    bgr = cv2.imread(input_file)
    if bgr is None:
        raise FileNotFoundError(f"Cannot read: {input_file}")
    grayImg = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)

    thr = cv2.adaptiveThreshold(
        grayImg, 255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY_INV,
        blockSize=51, C=10)

    cnts, _ = cv2.findContours(
        thr, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not cnts:
        return bgr.copy()
    biggest = max(cnts, key=cv2.contourArea)

    filledMask = np.zeros_like(grayImg)
    cv2.drawContours(filledMask, [biggest], -1, 255, thickness=cv2.FILLED)

    kk = frame_px // 2 * 2 + 1
    element = cv2.getStructuringElement(cv2.MORPH_RECT, (kk, kk))
    edgeMask = cv2.morphologyEx(filledMask, cv2.MORPH_GRADIENT, element)

    cleaned = cv2.inpaint(bgr, edgeMask, patch_r, cv2.INPAINT_TELEA)
    return cleaned

यह तरीका केवल बाहरी कंटूर पर टिकता है और मोटाई का अनुमान मॉर्फोलॉजिकल ग्रेडिएंट से लगाता है। व्यवहार में यह बाहरी बॉर्डर की सटीक स्ट्रोक चौड़ाई नहीं बचा पाता और इनपेंटिंग के दौरान किनारे धुंधले हो सकते हैं। साथ ही, यह हाथ जैसी रूप‑रेखाओं या अन्य गैर‑आयताकार आकृतियों पर भरोसेमंद ढंग से सामान्यीकृत नहीं होता।

असल समस्या क्या है

जब केवल बाहरी कंटूर निकाले जाते हैं, तो कंटूर पदानुक्रम में अंदर की आकृतियाँ अनदेखी रह जाती हैं। मतलब आप “कुछ बाहरी” तो पहचान लेते हैं, पर नेस्टेड क्षेत्रों की जानकारी खो देते हैं। इनपेंटिंग जोड़ देने पर पिक्सेल हटते हैं, परतों को अर्थपूर्ण रूप से अलग नहीं किया जाता। कर्नेल का चुनाव मोटाई का अनुमान देता है, लेकिन आउटलाइन बदलती रहती है और इनपेंटिंग चरण अक्सर दिखने वाले आर्टिफैक्ट छोड़ देता है। इस संदर्भ में जो सुझाव अक्सर आते हैं, उनमें RETR_EXTERNAL की जगह कंटूर हायरार्की देखना, सख्त काले‑सफेद चित्रों पर फ्लड‑फिल रणनीति अपनाना, और समाधान तय करने से पहले अधिक जटिल इनपुट पर विधि को परखना शामिल हैं।

एक ठोस तकनीक जो आयताकार से आगे भी काम करे

और भरोसेमंद तरीका यह है कि जो भी पिक्सेल सफेद नहीं है, उसे “आकृति” का हिस्सा मानें और कनेक्टेड कंपोनेंट्स खोजें। पढ़ने के क्रम में मिला पहला गैर‑सफेद पिक्सेल बाहरी‑से‑बाहरी आकृति का ही होगा, क्योंकि किसी भी आंतरिक क्षेत्र से पहले उसकी बाहरी सीमा दिख चुकी होगी। इस आधार पर आप मोटाई का अंदाज़ा लगाए बिना या इनपेंटिंग किए बिना, छवि को ‘बाहरी आकृति’ बनाम ‘बाकी सब’ में बाँट सकते हैं। यह मानकर चला जाता है कि पृष्ठभूमि सफेद है और सामग्री गैर‑सफेद।

import cv2
import numpy as np

src = cv2.imread("shapeToSeparate.jpg")
if src is None:
    raise FileNotFoundError("Cannot read input image")

g = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)

# आकृति के पिक्सेल के लिए 1, पृष्ठभूमि के लिए 0 (सफेद पृष्ठभूमि मानकर)
mask01 = (g < 250) * np.uint8(1)

# स्कैन क्रम में पहला गैर‑शून्य बाहरी आकृति का होगा
seed_idx = np.argmax(mask01)

# बाइनरी मास्क पर कनेक्टेड कंपोनेंट्स को लेबल करें
n_labels, labels = cv2.connectedComponents(mask01)

# बाहरी कंपोनेंट का लेबल मान
outer_id = labels.ravel()[seed_idx]

# बाहरी कंपोनेंट का बूलीयन मास्क
outer_mask = labels == outer_id

# साथ-साथ दृश्य बनाएं: बाईं ओर बाहरी, दाईं ओर भीतरी
h, w, ch = src.shape
canvas = np.full((h, 2 * w, ch), 255, dtype=np.uint8)

canvas[:, :w][outer_mask] = src[outer_mask]
canvas[:, w:][~outer_mask] = src[~outer_mask]

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

यह क्यों मायने रखता है

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

व्यावहारिक निष्कर्ष

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

संक्षेप में, शुरुआत में ही “आकृति” और “पृष्ठभूमि” को स्पष्ट करें, वास्तविक बाहरी आकृति खोजने के लिए कनेक्टेड कंपोनेंट्स अपनाएँ, और जिन पिक्सेल को रखना है उन्हें बदले बिना परिणामों को अलग करें। इससे किनारे तीखे रहते हैं और व्यवहार सुसंगत, भले ही बाहरी सीमा कोई साधारण फ्रेम न हो।

यह लेख StackOverflow के प्रश्न (लेखक: Tom) और chrslg के उत्तर पर आधारित है।