انتقل إلى المحتوى الرئيسي

الوحدة 6 — ControlNet: الوضعيّة والحوافّ والعمق

بعد خمس وحدات، نمتلك أدوات قويّة للتحكّم في مظهر الصورة (تعليمة، أسلوب، ملمس). لكن يبقى تحدٍّ لم نُعالجه: التحكّم في بنية الصورة نفسها. حين نريد كرسيًّا بوضعيّة محدّدة (ثلاثة أرباع، مائل قليلًا، بمنظور من الأعلى بـ15 درجة)، التعليمة النصّيّة تفشل والبذرة تُعطي وضعيّات عشوائيّة. الحلّ: ControlNet، شبكة إضافيّة تُضاف فوق U-Net وتقبل مدخلًا بنيويًّا (خريطة حوافّ، خريطة عمق، هيكل عظميّ) لإجبار التوليد على احترام هذا القيد.

الفكرة في جملة

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

المعالجات (preprocessors): من صورة إلى قيد

قبل تمرير الصورة إلى ControlNet، نمرّرها عبر معالج يستخرج القيد المطلوب. أشهر المعالجات:

  • Canny: يستخرج خطوط الحوافّ (Canny edge detection). مناسب للأثاث، البناء، السيّارات. يفرض شكلًا دقيقًا.
  • HED/PIDINet: خطوط أنعم من Canny، مناسبة للرسم اليدويّ والتصاميم الفنّيّة.
  • Depth: خريطة عمق (كلّ بكسل يُشير إلى مسافته من الكاميرا). مناسبة للتحكّم بالمنظور والحجم دون فرض تفاصيل الحوافّ.
  • OpenPose: هيكل عظميّ بشريّ (رأس، كتفان، يدان، ركبتان…). لا يعمل على الأثاث ولا على الحيوانات، فقط البشر.
  • Normal: خرائط الاتّجاه (normals) لتوجيه دقيق للسطوح.
  • Scribble: رسم يدويّ خربشة، النموذج يستكمل التفاصيل.
  • Lineart: خطوط رسم كرتونيّ، مفيد للتلوين من مسوّدة.

استعمال Canny على الكرسيّ

نأخذ الكرسيّ المرجعيّ، ونستخرج خطوطه بـCanny، ثمّ نُنتج نسخًا بأساليب مختلفة (ريفيّ، صناعيّ، شبابيّ) مع بقاء الشكل ثابتًا:

import cv2
import numpy as np
import torch
from PIL import Image
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel

# استخراج خريطة Canny
img = np.array(Image.open("chaise_reference.png"))
edges = cv2.Canny(img, threshold1=100, threshold2=200)
edges_rgb = np.stack([edges] * 3, axis=-1)
carte_canny = Image.fromarray(edges_rgb)

# تحميل ControlNet
controlnet = ControlNetModel.from_pretrained(
"diffusers/controlnet-canny-sdxl-1.0",
torch_dtype=torch.float16,
)
pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
controlnet=controlnet,
torch_dtype=torch.float16,
variant="fp16",
).to("cuda")

# توليد بأسلوب ريفيّ مع بقاء الشكل
image = pipe(
prompt="rustic wooden chair, farmhouse style, raw oak, matte finish",
image=carte_canny,
controlnet_conditioning_scale=0.7,
num_inference_steps=30,
).images[0]

عتبات Canny (100, 200) تُعطي خريطة متوسّطة الكثافة. عتبات منخفضة (50, 100) تُعطي حوافّ كثيرة (وتفرض تفاصيل صغيرة)، عتبات مرتفعة (200, 300) تُعطي حوافّ رئيسيّة فقط (وتترك للنموذج حريّة التفاصيل).

وزن التحكّم (controlnet_conditioning_scale)

المعلمة الأهمّ في ControlNet. بين 0 و2، الافتراضيّ 1.0.

  • 0.0: تجاهل تامّ للقيد. يعمل النموذج كأنّ ControlNet غير موجود.
  • 0.4-0.7: تحكّم مرن. الشكل العامّ يُحترَم، لكن النموذج يحرّك التفاصيل حسب التعليمة.
  • 0.9-1.1: تحكّم صارم. تُحترَم الحوافّ بدقّة كبيرة.
  • 1.5+: فرض قاسٍ، قد يُنتج تشوّهات (خطوط سوداء واضحة، ألوان مُشبَعة).

للأثاث حيث الشكل الدقيق مهمّ، 0.85-1.0 هو النطاق السعيد. للتصميم الحرّ حيث الشكل مجرّد إلهام، 0.5-0.7.

Depth: التحكّم بالمنظور بلا فرض التفاصيل

خريطة العمق تُخرِج من الصورة صورة رماديّة حيث الأبيض قريب والأسود بعيد. النموذج يفهم منظور المشهد بلا فرض شكل دقيق. مفيد جدًّا لـ:

  • نقل تكوين مشهد من صورة إلى صورة أخرى بأسلوب مختلف تمامًا.
  • الحفاظ على منظور الغرفة مع تغيير الأثاث كلّه.
  • التوليد ثلاثيّ الأبعاد الاصطناعيّ من مرجع.

يُستخرج العمق عادةً بنماذج مثل MiDaS أو DPT (يشغّلها معالج ControlNet جاهزًا).

تركيب أكثر من قيد

القوّة الحقيقيّة لـControlNet في تركيب قيود متعدّدة. ControlNet ثنائيّ (Canny + Depth) يُعطي كليهما: شكل الحوافّ الدقيق + المنظور. مكتبة diffusers تقبل قائمة ControlNet وقائمة صور قيود:

from diffusers import MultiControlNetModel

controlnets = MultiControlNetModel([controlnet_canny, controlnet_depth])
pipe.controlnet = controlnets

image = pipe(
prompt=prompt,
image=[carte_canny, carte_depth],
controlnet_conditioning_scale=[0.7, 0.5],
num_inference_steps=30,
).images[0]

قاعدة عمليّة: كلّ ControlNet إضافيّ يستهلك ذاكرة (~1.5 غيغا VRAM على SDXL). ثلاثة معًا على GPU بـ12 غيغا يبدأ في الاختناق. اثنان الحدّ العمليّ لأغلب الآلات.

الخيط الأحمر: خطّ منتجات كامل بوضعيّة واحدة

الهدف: كتالوج من عشر كراسٍ مختلفة (ريفيّ، صناعيّ، بلاستيك، معدن، إلخ) بنفس الزاوية والوضعيّة تمامًا لعرض متّسق. الطريقة:

  1. تصوير صورة مرجعيّة واحدة للكرسيّ الأساسيّ.
  2. استخراج خريطة Canny (نحتفظ بها كمرجع دائم).
  3. توليد عشر تعليمات مختلفة على نفس خريطة Canny بـcontrolnet_conditioning_scale=0.9.
  4. الحصول على عشر صور بأساليب مختلفة، نفس الزاوية والوضعيّة والحجم.

هذا مستحيل بدون ControlNet: عشر توليدات نصّ إلى صورة تُعطي عشر زوايا مختلفة. عشر توليدات صورة إلى صورة تُخرَّب الأسلوب عند strength عالٍ. ControlNet الحلّ الوحيد.

قاعدة قرار

اسأل نفسك: «ما الذي يجب أن يبقى ثابتًا؟». الحوافّ الدقيقة → Canny. المنظور والحجم → Depth. وضعيّة إنسان → OpenPose. تخطيط عامّ → Scribble. لا تُحمّل نموذج ControlNet لا يخدم قيدًا حقيقيًّا في مشروعك.

الخلاصة

  • ControlNet يُضيف تحكّمًا بنيويًّا فوق U-Net عبر خريطة قيد مُستخرَجة من مرجع.
  • المعالج (Canny، Depth، OpenPose…) يُحدّد نوع القيد؛ الاختيار يعتمد على ما نريد فرضه.
  • وزن التحكّم (controlnet_conditioning_scale) بين 0.5 و1.1 هو النطاق العمليّ؛ فوق 1.5 تظهر تشوّهات.
  • التركيب بين قيدين (Canny + Depth) يُعطي تحكّمًا شاملًا، لكنّه يستهلك ذاكرة إضافيّة.