از رنگ استفاده کنیم یا خاکستری کنیم عکس را؟

saalek110

Well-Known Member
raveah mamol ya raveshe estefadeh az rang

روش رایج را بارها و بارها خوانده ام ...منظورم روشهای خاکستری کردن و بی توجهی به رنگ است ....سوالی که دارم اینه که آیا رنگ گاهی ابزار بهتری برای پیدا کردن کاغذ یا مربع نیست؟ مثلا کاغذ اگر صورتی باشه باز بیاییم تبدیل به خاکسترس کنسم و رنگ را نابود کنیم؟



این یک سوال بسیار هوشمندانه و عمیق است. دقیقاً به نقطه‌ای اشاره کردید که مرز بین «پردازش تصویر کلاسیک» و «درک واقعی از صحنه» است.

پاسخ کوتاه: بله، کاملاً حق با شماست. در بسیاری از موارد، نادیده گرفتن رنگ مثل این است که با چشم‌بند به دنبال یک سیب قرمز در میان برگ‌های سبز بگردید. رنگ یک سرنخ بسیار قدرتمند است که متأسفانه در بسیاری از آموزش‌های مقدماتی نادیده گرفته می‌شود.

اما بیایید عمیق‌تر بررسی کنیم که چرا این اتفاق می‌افتد و چه زمانی باید از رنگ استفاده کنیم.

---

چرا آموزش‌های مقدماتی اغلب رنگ را نادیده می‌گیرند؟

۱. سادگی و عمومیت: یک الگوریتم مبتنی بر لبه و کانتور، روی هر تصویری کار می‌کند، چه رنگی، چه خاکستری، چه عکس یک نقاشی قدیمی. آموزش این روش، مفاهیم بنیادین (گرادیان، فرکانس، مورفولوژی) را منتقل می‌کند که سنگ بنای همه چیز هستند. رنگ، یک لایه پیچیدگی اضافه می‌کند.

۲. پایداری نور: بزرگترین دشمن رنگ، نور است. یک کاغذ «صورتی» زیر نور زرد داخل خانه، زیر نور سفید مهتابی، و در سایه، سه رنگ کاملاً متفاوت در دوربین ثبت می‌کند. الگوریتمی که روی لبه کار می‌کند، نسبت به این تغییرات بسیار مقاوم‌تر از الگوریتمی است که روی یک محدوده رنگی خاص (مثلاً [140, 100, 180] تا [180, 255, 255]) تنظیم شده است.

۳. توهم همبستگی: یک مربع آبی و یک مربع قرمز، هر دو «مربع» هستند. ما انسان‌ها اولویت را به «شکل» می‌دهیم، پس الگوریتمی که صرف نظر از رنگ، مربع را پیدا کند، «هوشمندانه‌تر» به نظر می‌رسد. اما فراموش می‌کنیم که رنگ می‌تواند آسان‌ترین راه برای جدا کردن شیء از پس‌زمینه باشد.

---

پس کی و چگونه از رنگ برای پیدا کردن مربع استفاده کنیم؟

وقتی که ما کنترل صحنه را داریم یا اطلاعات پیشینی داریم:

· می‌دانیم کاغذ حتماً صورتی است.
· می‌دانیم پس‌زمینه صورتی نیست.

در این حالت، نادیده گرفتن رنگ یک اشتباه راهبردی است! شما با رنگ می‌توانید یک «ماسک» فوق‌العاده تمیز و قدرتمند بسازید و کار را برای مراحل بعدی بسیار آسان‌تر کنید.

رویکرد جایگزین: بخش‌بندی بر اساس رنگ

به جای اینکه منتظر لبه‌یابی بمانیم، اول بر اساس رنگ ناحیه را جدا می‌کنیم. این کار با تبدیل تصویر به فضای رنگی HSV (Hue, Saturation, Value) انجام می‌شود، نه RGB.

چرا HSV؟
RGB یک فضا برای نمایش رنگ است. HSV یک فضا برای توصیف رنگ به زبان انسان است.

· H (فام): خودِ رنگ (قرمزی، آبی، صورتی). این همان چیزی است که ما «صورتی» می‌نامیم.
· S (اشباع): خلوص رنگ. صورتی پررنگ یا صورتی کم‌رنگ و محو.
· V (ارزش/روشنایی): میزان نور. صورتی تیره یا صورتی روشن.

در HSV، تغییرات نور (سایه و روشنایی) عمدتاً کانال V را تغییر می‌دهند و H نسبتاً پایدار می‌ماند. این دقیقاً همان مقاومتی است که در RGB نداریم.

---

Pipeline جدید بر اساس رنگ

بیایید یک pipeline بنویسیم که برای پیدا کردن «کاغذ صورتی» بهینه شده است.

```python
import cv2
import numpy as np

def find_square_by_color(image_path):
img = cv2.imread(image_path)
if img is None:
print("خطا: تصویر یافت نشد.")
return
output_img = img.copy()

# ۱. تبدیل به HSV - کلید موفقیت
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# ۲. تعریف محدوده رنگ صورتی در فضای HSV
# نکته طلایی: این محدوده نیاز به تنظیم دارد!
# برای شروع، دو طیف برای صورتی (چون قرمز در HSV دور می‌زند) تعریف می‌کنیم.
lower_pink1 = np.array([0, 50, 50])
upper_pink1 = np.array([10, 255, 255])
lower_pink2 = np.array([145, 50, 50])
upper_pink2 = np.array([180, 255, 255])

# ۳. ساخت ماسک: هر پیکسلی که در این محدوده باشد سفید، بقیه سیاه
mask1 = cv2.inRange(hsv, lower_pink1, upper_pink1)
mask2 = cv2.inRange(hsv, lower_pink2, upper_pink2)
color_mask = cv2.bitwise_or(mask1, mask2)

# ۴. تمیزکاری ماسک (بسیار مهم!)
# ماسک خام معمولاً نویز و حفره‌های کوچک دارد.
kernel = np.ones((5, 5), np.uint8)
# Erosion برای حذف نویزهای سفید ریز
color_mask = cv2.erode(color_mask, kernel, iterations=1)
# Dilation برای پر کردن حفره‌های کوچک داخل ناحیه
color_mask = cv2.dilate(color_mask, kernel, iterations=2)

# ۵. حالا از روی این ماسکِ قدرتمند، کانتور می‌گیریم!
# برخلاف لبه‌یابی، اینجا پس‌زمینه تقریباً کامل حذف شده است.
contours, _ = cv2.findContours(color_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

square_count = 0
for cnt in contours:
area = cv2.contourArea(cnt)
# این بار می‌توانیم آستانه مساحت را بزرگ‌تر بگیریم چون بعید است نویز کل سطح کاغذ را بپوشاند.
if area < 1000:
continue

# تقریب چندضلعی برای پیدا کردن مربع (همان روش قبلی)
epsilon = 0.02 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)

if len(approx) == 4:
square_count += 1
cv2.drawContours(output_img, [approx], -1, (0, 255, 0), 3)

# ... (بقیه کد برای چاپ مختصات و نمایش، مشابه قبل)

# نمایش ماسک رنگی - اینجا می‌بینید که کار چقدر تمیز انجام شده
cv2.imshow("Powerful Color Mask", color_mask)
cv2.imshow("Detection Result", output_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

# اجرا با رنگ
find_square_by_color('pink_paper_with_square.jpg')
```

مقایسه این دو رویکرد
a1.jpg
روش مزایا معایب بهترین زمان استفاده
Cannt + Contours عمومی، مستقل از رنگ، مقاوم به تغییرات نور. شکننده در پس‌زمینه‌های شلوغ، حساس به سایه و نویز لبه. وقتی چیزی از رنگ شیء نمی‌دانیم، یا رنگ آن مثل پس‌زمینه است.
Color Mask + Contours فوق‌العاده مقاوم به شلوغی پس‌زمینه، سگمنتیشن تمیز، سریع. نیازمند تنظیم دقیق محدوده رنگ، شکننده در برابر تغییرات شدید نور (هرچند HSV کمک می‌کند). وقتی رنگ شیء متمایز و از پیش معلوم است. مثل کاغذ صورتی شما!

نتیجه‌گیری: کدام راه درست است؟

هیچ‌کدام و هر دو! هدف، «پیدا کردن چیزها» بود. یک بیننده‌ی خبره، این ابزارها را مثل یک جعبه آچار می‌بیند.

شما برای مسئله‌ی «کاغذ صورتی»، باید بروید سراغ پایپلاین مبتنی بر رنگ. اما اگر روزی خواستید یک اپلیکیشن اسکنر بسازید که کاربر با موبایلش از هر سندی عکس بگیرد (قبض سفید، نامه زرد، مجله رنگی)، آن‌وقت pipeline مبتنی بر لبه و سپس پیدا کردن بزرگ‌ترین کانتور ۴ ضلعی (max(contours, key=cv2.contourArea)) منطقی‌ترین انتخاب است.

پس بله، سوال شما کاملاً بجاست. رنگ را «نابود» نکنید، مگر اینکه دلیل خوبی برایش داشته باشید. همیشه از خودتان بپرسید: "چه چیزی این شیء را از محیطش متمایز می‌کند؟" اگر پاسخ «رنگ» است، پس HSV جعبه‌ابزار اصلی شماست.
 

saalek110

Well-Known Member
koja az rang estefadeh konim va koja nakonim

بحث خوبی کردی....می توانی باز بگی کجاها باید از رنگ استفده کنیم و کجاها رنگ را ندیده بگیریم


deepseek:
بیایید این موضوع را با یک دیدگاه مهندسی و راهبردی جمع‌بندی کنیم. می‌خواهیم یک «درخت تصمیم» در ذهنمان بسازیم که هر وقت به یک مسئله‌ی «پیدا کردن چیزها» خوردیم، سریع بدانیم رنگ را در راه‌حل خود دخیل کنیم یا خیر.

اصل اساسی این است: رنگ، یک ویژگی با پایداری مشروط است. ما باید شرایط پایداری آن را بسنجیم.

---

بخش اول: کجاها باید رنگ را «داور نهایی» کنیم؟

(یعنی رنگ، بهترین و مطمئن‌ترین سرنخ است)

در این سناریوها، نادیده گرفتن رنگ یک اشتباه محاسباتی است و کار را صد برابر سخت‌تر می‌کند.

۱. وقتی رنگ، ذات شیء است ("تعریف شیء با رنگ")

· مثال‌ها:
· چراغ راهنمایی: یک چراغ سبز و یک چراغ قرمز از نظر شکل و لبه یکی هستند. اینجا فقط رنگ است که معنا می‌دهد (خطر/امنیت).
· سیم‌های برق رنگی: برای پیدا کردن سیم قرمز در یک تابلو برق شلوغ.
· دکمه‌های رنگی روی یک پنل: دکمه سبز "شروع" و دکمه قرمز "توقف".
· پالت‌های رنگی: پیدا کردن یک رنگ خاص در یک جعبه مداد رنگی.
· راه حل: بخش‌بندی بر اساس HSV و سپس تحلیل کانتورها. اینجا رنگ، هدف اصلی است، نه یک ویژگی کمکی.

۲. وقتی پس‌زمینه نامعلوم و بسیار شلوغ (Cluttered) است، اما رنگ شیء متمایز است.

· سناریوی واقعی: پیدا کردن توپ تنیس زرد روی چمن، یا یک بطری آبی در یک کارخانه با پس‌زمینه‌ی فلزات و سیم‌های درهم.
· تحلیل: لبه‌یابی (Canny) اینجا جهنم می‌شود؛ هزاران لبه از پس‌زمینه داریم. اما رنگ، یک فیلتر جادویی است که همه چیز به جز توپ یا بطری را نابود می‌کند.
· راه حل: cv2.inRange در HSV. این یک «بخش‌بندی معنایی» ساده و فوق‌العاده مؤثر است.

۳. وقتی با سطوح یکدست بدون بافت سر و کار داریم.

· مثال: کاغذ رنگی، کارت‌های بازی، بنرهای تبلیغاتی، لباس‌های ساده.
· تحلیل: این اشیاء ممکن است لبه‌های قوی ایجاد نکنند (مخصوصاً اگر پس‌زمینه هم روشن باشد)، یا لبه‌هایشان بر اثر تا خوردگی مخدوش شود. اما یک ناحیه‌ی پیوسته از یک رنگ خاص هستند.
· راه حل: استفاده از cv2.findContours مستقیماً روی ماسک رنگی. این روش از لبه‌یابی کنی قدرتمندتر است، چون سایه‌های ملایم داخل کاغذ صورتی، در ماسک رنگی دیده نمی‌شوند اما در گرادیان لبه، یک خط اضافی تولید می‌کنند.

---

بخش دوم: کجاها باید رنگ را «ندیده بگیریم»؟

(یعنی رنگ، یک عامل فریبنده یا ناپایدار است و باید به شکل و هندسه تکیه کنیم)

۱. وقتی هدف ما «شکل کلی» است نه «ظاهر رنگی».

· مثال: تشخیص تمام پنجره‌های یک ساختمان، یا شمارش تمام مربع‌های روی یک برگه، صرف‌نظر از اینکه چه رنگی دارند.
· تحلیل: اگر یک مربع قرمز و یک مربع آبی داریم، راه‌حلی که بر اساس رنگ باشد، یکی را پیدا می‌کند و دیگری را از دست می‌دهد. ما باید یک راه‌حل عمومی برای «مربع بودن» پیدا کنیم.
· راه حل: Canny -> Contours -> approxPolyDP. اینجا رنگ را آگاهانه نادیده می‌گیریم تا به یک مفهوم هندسی برسیم.

۲. وقتی شرایط نوری غیرقابل پیش‌بینی و متغیر است.

· سناریوی واقعی: یک ربات که باید در طول روز و شب، زیر نور آفتاب و سایه، کار کند. یا یک اپلیکیشن موبایل که کاربر در هر شرایط نوری از سندش عکس می‌گیرد.
· تحلیل: یک کاغذ سفید در نور غروب، نارنجی به نظر می‌رسد. تنظیم محدوده‌ی رنگی برای این همه حالت غیرممکن است. اتکا به رنگ در اینجا یعنی شکست حتمی. هندسه (لبه‌ها) اما تحت تأثیر رنگ نور تغییر نمی‌کند، فقط ممکن است کنتراست کم یا زیاد شود.
· راه حل: تکیه بر روش‌های مبتنی بر گرادیان و لبه که به جای رنگ مطلق، به «تفاوت روشنایی» نسبی اهمیت می‌دهند.

۳. وقتی شیء ذاتاً چندرنگ، طرح‌دار یا شفاف است.

· مثال: پیدا کردن یک جعبه‌ی مقوایی که روش آن پر از نوشته و لوگو است، یک فنجان سرامیکی طرح‌دار، یا یک لیوان شفاف.
· تحلیل: نمی‌توان برای این اشیاء یک «محدوده‌ی رنگ» تعریف کرد. لیوان شفاف که کلاً رنگ پس‌زمینه را به خود می‌گیرد. جعبه‌ی مقوایی هم ده‌ها رنگ مختلف در خود دارد.
· راه حل: اینجا دوباره به سراغ لبه‌ها می‌رویم. حتی اگر داخل جعبه پر از رنگ باشد، مرز بیرونی جعبه با پس‌زمینه معمولاً یک لبه‌ی قوی ایجاد می‌کند. برای لیوان شفاف هم باید از انعکاس‌ها و لبه‌های ضعیف استفاده کنیم.

۴. در کاربردهای صنعتی با کنتراست کنترل‌شده.

· سناریو: روی یک خط تولید، قطعات همیشه از یک جنس، با یک رنگ و روی یک نوار نقاله با رنگ ثابت می‌آیند.
· بحث: اینجا یک وسوسه وجود دارد: «چون رنگ قطعه ثابت است، با رنگ پیدا کنیم». اما اگر به دلیل سایه یا تابش نور، رنگ قطعه در HSV جابجا شود، برنامه از کار می‌افتد.
· راه حل حرفه‌ای: یک نورپردازی اختصاصی و قوی (مثلاً نور قرمز) استفاده می‌کنند که تمام تغییرات نور محیط را خنثی کند، و سپس با یک دوربین تک‌رنگ (Monochrome) و فیلتر، تصویر را می‌گیرند. اینجا کلاً رنگ حذف می‌شود تا به حداکثر پایداری برسیم. تکیه‌گاه اصلی، شدت نور (Intensity) است، نه رنگ.

---

یک مدل ذهنی ساده برای تصمیم‌گیری

هر وقت به مسئله‌ای برخوردید، این سه سوال را به ترتیب از خود بپرسید:

سوال کلیدی اگر پاسخ "بله" است اگر پاسخ "خیر" است
۱. آیا «رنگ خاص» بخشی از تعریف خود شیء است؟ (مثلاً چراغ قرمز) حتماً از رنگ استفاده کن. رنگ هدف نهایی است. برو سراغ سوال بعدی.
۲. آیا می‌توانی رنگ شیء را به راحتی از پس‌زمینه‌ی پیش‌بینی‌نشده جدا کنی؟ (مثلاً توپ زرد روی چمن شلوغ) از رنگ به عنوان «فیلتر جداکننده» استفاده کن. این هوشمندانه‌ترین راه است. برو سراغ سوال بعدی.
۳. آیا شرایط نوری کاملاً تحت کنترل توست؟ (مثلاً یک دستگاه آزمایشگاهی) می‌توانی از رنگ استفاده کنی، اما معمولاً روش‌های مبتنی بر شکل برای محیط‌های کنترل‌شده هم ساده و عالی‌اند. رنگ را فراموش کن. روی لبه‌ها، گوشه‌ها و هندسه تمرکز کن.

خلاصه‌ی نهایی:
رنگ، شمشیر دو لبه است. برای مسائل خاص، معجزه می‌کند و راه را دور می‌زند. اما برای ساخت «راه‌حل‌های مقاوم و عمومی»، شیوه‌ی مبتنی بر شکل و لبه، ستون فقرات و گزینه‌ی امن‌تر است. یک چشم‌بینای حرفه‌ای، هر دو را در جعبه ابزار خود دارد و می‌داند بسته به پایداری شرایط، کدام را بیرون بکشد.

a2.jpg
 

saalek110

Well-Known Member
a3 moshkelat va ravesh ha

بیایید دقیقاً روی میز تشریح روش مبتنی بر رنگ بشینیم. قول می‌دهم تا آخر این بحث، شما تبدیل به یک «جراح رنگ» در OpenCV بشوید و دقیقاً بدانید کجای کار را باید ببرید و کجا بخیه بزنید.

بخش اول: روش اجرا - کالبدشکافی یک پایپلاین رنگی

بر خلاف ظاهر ساده‌اش، یک پایپلاین موفق مبتنی بر رنگ، یک فرآیند چند مرحله‌ای و دقیق است.

```python
import cv2
import numpy as np

def surgical_color_detection(image_path):
# --- مرحله ۰: خواندن تصویر ---
# نکته: همیشه تصویر اصلی را برای نمایش نهایی نگه دارید.
img_bgr = cv2.imread(image_path)
if img_bgr is None: return
output_img = img_bgr.copy()

# --- مرحله ۱: انتخاب فضای رنگی درست (مهم‌ترین تصمیم) ---
# BGR (پیش‌فرض OpenCV): فاجعه است برای تعریف رنگ!
# سه کانال آن (آبی، سبز، قرمز) به شدت به نور وابسته‌اند.
# یک پیکسل صورتی در سایه و زیر نور، در BGR سه عدد کاملاً متفاوت دارد.

# HSV: انتخاب طلایی برای ۹۰٪ کارها.
# H (فام): خودِ رنگ را از ۰ تا ۱۸۰ کد می‌کند (مثلاً صورتی ≈ ۱۵۰-۱۷۰).
# S (اشباع): خلوص رنگ. ۰ = خاکستری، ۲۵۵ = غلیظ.
# V (ارزش/روشنایی): میزان روشنایی رنگ. ۰ = سیاه، ۲۵۵ = سفید.
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

# --- مرحله ۲: تعریف محدوده رنگ (هنر تنظیم پارامتر) ---
# این کار اصلاً با حدس زدن عدد انجام نمی‌شود!
# روش حرفه‌ای: از Trackbar استفاده کنید (در ادامه توضیح می‌دهم).

# مشکل: رنگ قرمز در فضای HSV، دور دایره رنگ پیچیده است.
# یعنی هم نزدیک ۰ است و هم نزدیک ۱۸۰.
# برای رنگ‌های خانواده قرمز (قرمز، صورتی، نارنجی) باید دو ماسک بسازیم و جمع کنیم.

# فرض: می‌خواهیم کاغذ صورتی را پیدا کنیم.
# بخش اول: صورتی مایل به بنفش (نزدیک ۱۷۰)
lower_pink = np.array([145, 60, 50])
upper_pink = np.array([170, 255, 255])

# بخش دوم: صورتی مایل به قرمز (نزدیک ۰) - اگر رنگ کاغذ به قرمز بزند
lower_red = np.array([170, 60, 50])
upper_red = np.array([180, 255, 255])


# --- مرحله ۳: ساخت ماسک خام ---
# cv2.inRange: موتور اصلی. هر پیکسل در محدوده = ۲۵۵، بقیه = ۰
mask1 = cv2.inRange(img_hsv, lower_pink, upper_pink)
mask2 = cv2.inRange(img_hsv, lower_red, upper_red)
raw_mask = cv2.bitwise_or(mask1, mask2)

# --- مرحله ۴: تمیزکاری ماسک (جایی که ۹۰٪ افراد شکست می‌خورند) ---
# ماسک خام معمولاً یک آشغال‌دانی دیجیتال است!
# پر از سوراخ‌های ریز (جایی که رنگ کمی تغییر کرده)
# و نویزهای سفید (پیکسل‌های تصادفی همرنگ در پس‌زمینه).

# ۴.۱: عملیات مورفولوژیکی - حذف نویز (Erosion)
# Erosion لبه‌های اشیاء سفید را می‌خورد. پس نویزهای ریز سفید را کاملاً نابود می‌کند.
kernel = np.ones((5, 5), np.uint8)
mask_denoised = cv2.erode(raw_mask, kernel, iterations=1)

# ۴.۲: عملیات مورفولوژیکی - پر کردن حفره‌ها (Dilation)
# Dilation اشیاء سفید را فربه‌تر می‌کند. الان سوراخ‌های داخل کاغذ پر می‌شوند.
mask_closed = cv2.dilate(mask_denoised, kernel, iterations=2)

# ۴.۳: (اختیاری ولی قدرتمند) پیدا کردن بزرگترین ناحیه متصل
# اگر بدانیم کاغذ، بزرگترین شیء صورتی در تصویر است، هر چیز دیگری را حذف می‌کنیم.
contours, _ = cv2.findContours(mask_closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
final_mask = np.zeros_like(mask_closed) # یک ماسک سیاه جدید
if contours:
largest_contour = max(contours, key=cv2.contourArea)
cv2.drawContours(final_mask, [largest_contour], -1, 255, -1) # فقط بزرگترین را سفید کن

# --- مرحله ۵: استفاده از ماسک نهایی ---
# حالا یک ماسک تمیز و بی‌نقص داریم. می‌توانیم:
# ۱. خود کاغذ را از پس‌زمینه جدا کنیم:
isolated_paper = cv2.bitwise_and(img_bgr, img_bgr, mask=final_mask)

# ۲. یا از روی ماسک، کانتور بگیریم برای پیدا کردن شکل مربع (مقاوم‌ترین روش!)
final_contours, _ = cv2.findContours(final_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in final_contours:
# ... مراحل تقریب چندضلعی و پیدا کردن مربع ...

# نمایش نتایج برای دیباگ
cv2.imshow("1. Raw Mask (Noisy & Holes)", raw_mask)
cv2.imshow("2. Cleaned Mask (Morphology)", mask_closed)
cv2.imshow("3. Final Mask (Largest Object Only)", final_mask)
cv2.imshow("4. Isolated Paper", isolated_paper)

cv2.waitKey(0)
cv2.destroyAllWindows()
```

چگونه محدوده HSV را پیدا کنیم؟ (هنر ترک‌بار)

هرگز، به هیچ وجه، اعداد را از روی هوا حدس نزنید. این کد جادویی را یک بار برای همیشه یاد بگیرید:

```python
import cv2
import numpy as np

def empty(a): # یک تابع خالی برای ترک‌بار
pass

def find_hsv_range_with_trackbar(image_path):
img = cv2.imread(image_path)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

cv2.namedWindow("HSV Tuner")
cv2.resizeWindow("HSV Tuner", 640, 400)

# ساخت ۶ ترک‌بار برای حداقل و حداکثر H, S, V
cv2.createTrackbar("H Min", "HSV Tuner", 0, 180, empty)
cv2.createTrackbar("H Max", "HSV Tuner", 180, 180, empty)
cv2.createTrackbar("S Min", "HSV Tuner", 0, 255, empty)
cv2.createTrackbar("S Max", "HSV Tuner", 255, 255, empty)
cv2.createTrackbar("V Min", "HSV Tuner", 0, 255, empty)
cv2.createTrackbar("V Max", "HSV Tuner", 255, 255, empty)

while True:
h_min = cv2.getTrackbarPos("H Min", "HSV Tuner")
h_max = cv2.getTrackbarPos("H Max", "HSV Tuner")
s_min = cv2.getTrackbarPos("S Min", "HSV Tuner")
s_max = cv2.getTrackbarPos("S Max", "HSV Tuner")
v_min = cv2.getTrackbarPos("V Min", "HSV Tuner")
v_max = cv2.getTrackbarPos("V Max", "HSV Tuner")

lower = np.array([h_min, s_min, v_min])
upper = np.array([h_max, s_max, v_max])

mask = cv2.inRange(img_hsv, lower, upper)
result = cv2.bitwise_and(img, img, mask=mask)

# نمایش ماسک و نتیجه به صورت زنده
cv2.imshow("HSV Tuner", np.hstack([img, result]))

if cv2.waitKey(1) & 0xFF == ord('q'):
break

cv2.destroyAllWindows()
print(f"محدوده نهایی: lower = np.array([{h_min}, {s_min}, {v_min}])")
print(f" upper = np.array([{h_max}, {s_max}, {v_max}])")

# اجرا کن و با اسلایدرها بازی کن تا فقط شیء مورد نظرت سفید شود.
# find_hsv_range_with_trackbar('pink_paper.jpg')
```

---

بخش دوم: مشکلات روش مبتنی بر رنگ - چالش‌ها و درمان‌ها

اینجا بیمارستان رنگ است. باید بدانیم چه بیماری‌هایی در کمین نشسته‌اند.

۱. بیماری: وسواس نوری - بزرگترین قاتل

· علامت: برنامه‌تان صبح عالی کار می‌کند، عصر فاجعه است. یا در یک سمت اتاق خوب است، در سمت دیگر کور می‌شود.
· علت: نور خورشید، لامپ مهتابی و لامپ رشته‌ای، طیف‌های نوری متفاوتی دارند. متامریسم (Metamerism): یک کاغذ صورتی زیر نور A و B، برای دوربین دو رنگ متفاوت است. کانال V و حتی H در inRange جابجا می‌شود.
· درمان:
1. تراز سفیدی (White Balance): دوربین را با یک کارت سفید کالیبره کنید. یا از cv2.xphoto.createSimpleWB() استفاده کنید.
2. باز کردن محدوده: بزرگ‌ترین قاتل، تنظیم محدوده‌ی خیلی سفت و سخت است. ببینید در بدترین شرایط نوری، H شیء شما چند می‌شود و محدوده را بازتر بگذارید. این ممکن است نویز را زیاد کند (با مورفولوژی بعداً تمیزش می‌کنیم).
3. استفاده از نسبت رنگ: به جای بازه مطلق، از این اصل استفاده کنید: "پیکسل قرمز، یعنی R خیلی بزرگتر از G و B است". این کار با cv2.split کانال‌ها و محاسبات ماتریسی ساده قابل انجام است و مقاومت نوری را کمی بالا می‌برد.
4. نورپردازی اختصاصی (راه‌حل صنعتی): اتاق تاریک + نور ثابت. تکلیف را یکسره می‌کند.

۲. بیماری: شباهت مرگ‌بار

· علامت: یک شیء دیگر در تصویر هم دقیقاً همان رنگ صورتی را دارد و برنامه‌تان دیوانه می‌شود.
· درمان:
1. سلسله‌مراتب فیلترها: از رنگ برای «پیشنهاد دادن» نواحی کاندید استفاده کنید، نه «تصمیم نهایی». بگویید: "ناحیه‌ای که همرنگ صورتی است و چهار گوشه دارد". این همان هنر ترکیب رنگ با شکل است که قبلاً گفتیم.
2. فیلتر مساحت: از cv2.contourArea برای انتخاب بزرگترین یا کوچکترین ناحیه استفاده کنید (اگر منطقی باشد).

۳. بیماری: ناهمگونی رنگ شیء

· علامت: سطح کاغذ کاملاً یکرنگ نیست. سایه دارد، تا خوردگی دارد، قسمتی برق زده است. ماسک شما پر از سوراخ است.
· علت: inRange یک تصمیم باینری سخت (۰ یا ۱) بر اساس تک‌تک پیکسل‌ها می‌گیرد.
· درمان:
1. عملیات مورفولوژیکی بستن (Closing): دقیقاً با cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) که ترکیب Dilation و سپس Erosion است و سوراخ‌های کوچک را مثل بتونه پر می‌کند.
2. فیلتر مساحت: بزرگترین کانتور را به عنوان شیء اصلی انتخاب کنید (روشی که در کد نشان دادم).
3. پیدا کردن کانتور روی ماسک تمیز شده: این خیلی مقاوم‌تر از اجرای Canny روی تصویر اصلی است، چون پس‌زمینه کلاً حذف شده.

۴. بیماری: وابستگی به مقیاس و فاصله

· علامت: در فاصله نزدیک، کاغذ را تشخیص می‌دهد. در فاصله دور، نه.
· علت: inRange ذاتاً به اندازه کاری ندارد. مشکل از wo جای دیگری است: با دور شدن، اعوجاج لنز، تاری و نویز باعث می‌شوند رنگ کاغذ با پس‌زمینه قاطی شود و لبه‌هایش محو.
· درمان:
1. هرم تصاویر (Image Pyramids): تصویر را در چند مقیاس کوچک کنید و روی هر کدام الگوریتم را اجرا کنید (cv2.pyrDown).
2. تار کردن گوسی قبل از ماسک: یک GaussianBlur خفیف روی تصویر HSV قبل از inRange، نویز را کم می‌کند و لبه‌ها را نرم‌تر می‌کند.

۵. بیماری: سربار محاسباتی

· علامت: برنامه‌تان کند است (مخصوصاً روی Raspberry Pi).
· درمان:
1. ابتدا تصویر را به یک رزولوشن معقول (مثلاً ۴۸۰p) کاهش دهید.
2. inRange و عملیات مورفولوژی روی آرایه‌های NumPy بسیار سریع و بهینه هستند. نقطه ضعف اصلی، findContours روی صحنه‌های شلوغ است. با تمیز کردن ماسک، سرعت را هم بالا برده‌اید.

نتیجه‌گیری نهایی: فلسفه استفاده از رنگ

رنگ را یک «فیلتر کاهش فضای جستجو» ببینید، نه یک «دستورالعمل تشخیص نهایی».

· کاربرد ایده‌آل: به کمک رنگ، از شر ۹۰٪ پیکسل‌های بی‌ربط تصویر خلاص می‌شوید.
· گام بعدی: روی ۱۰٪ باقی‌مانده (که حالا خیلی تمیز و خالص است)، به دنبال اشکال هندسی، الگوها و منطق بگردید.

این ترکیب، شاه‌کلید حل مسائل واقعی و پیچیده است. ابزار HSV و Trackbar را هم مثل چاقوی جراحی همیشه در جیب بغل داشته باشید.
 

saalek110

Well-Known Member
a4 trackbar chieh

دقیقاً متوجه شدید. ترک‌بار (Trackbar) در OpenCV یک ابزار گرافیکی تعاملی است، دقیقاً مثل یک اسلایدر (لغزنده) که می‌توانید با موس آن را بکشید.

به جای اینکه کورکورانه اعداد را در کد تغییر دهید و برنامه را دوباره و دوباره اجرا کنید، ترک‌بار به شما اجازه می‌دهد در لحظه (Real-time) اثر تغییر هر عدد را روی تصویر ببینید تا به محدوده طلایی برسید. این دقیقاً همان «برنامه‌ای برای تعیین محدوده رنگ‌ها» است که حدس زدید.

بیایید این ابزار جادویی را کامل بشناسیم و یک نسخه‌ی حرفه‌ایتر از آن را بررسی کنیم.

ترک‌بار دقیقاً چه شکلی است؟

وقتی برنامه را اجرا می‌کنید، پنجره‌ای مثل این (اما با اسلایدرهای OpenCV) باز می‌شود:

[Title: HSV Tuner]
H Min: [======|====] 145
H Max: [==========|] 170
S Min: [==|========] 60
S Max: [==========|] 255
V Min: [=|=========] 50
V Max: [==========|] 255

شما اسلایدر H Min را می‌کشید، همزمان ماسک رنگ روی تصویر تغییر می‌کند و می‌بینید که کدام قسمت‌ها سفید (انتخاب شده) می‌شوند. هدف این است که فقط شیء شما سفید شود و بقیه تصویر سیاه.

---

برنامه پیشرفته‌تر: اتاق جراحی رنگ با قابلیت ذخیره و بارگذاری

برنامه قبلی مقدماتی بود. این نسخه چند قابلیت کاربردی و حیاتی دیگر دارد که آن را از یک ابزار ساده به یک دستیار حرفه‌ای تبدیل می‌کند:

1. قابلیت ذخیره و بارگذاری تنظیمات: بعد از یک ساعت تنظیم، می‌توانید اعداد را در یک فایل ذخیره کنید.
2. فیلتر مورفولوژی زنده: می‌بینید که اگر نویز دارید، Erosion و Dilation چه تأثیری دارند.
3. نمایش خودِ رنگ انتخاب‌شده: یک پَچ رنگی گوشه تصویر می‌بینید تا مطمئن شوید رنگی که انتخاب کرده‌اید، همان رنگی است که می‌خواهید.

```python
import cv2
import numpy as np

# --- ۰. توابع کمکی ---
def empty(_):
"""تابع خالی برای ترک‌بارها"""
pass

def stack_images(scale, img_array):
"""چسباندن چند تصویر کنار هم برای نمایش"""
rows = len(img_array)
cols = len(img_array[0])
w, h = img_array[0][0].shape[1], img_array[0][0].shape[0]
# ... (تابعی ساده برای چیدن تصاویر در یک پنجره) ...
# برای سادگی، فقط از np.hstack استفاده می‌کنیم.
return np.hstack([np.hstack(row) for row in img_array])

# --- ۱. پنجره و ترک‌بارها ---
window_name = "Color Surgeon - HSV Tool"
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, 900, 500)

# ترک‌بارهای HSV
cv2.createTrackbar("H Min", window_name, 0, 180, empty)
cv2.createTrackbar("H Max", window_name, 180, 180, empty)
cv2.createTrackbar("S Min", window_name, 0, 255, empty)
cv2.createTrackbar("S Max", window_name, 255, 255, empty)
cv2.createTrackbar("V Min", window_name, 0, 255, empty)
cv2.createTrackbar("V Max", window_name, 255, 255, empty)

# ترک‌بارهای مورفولوژی (برای تمیزکاری زنده)
cv2.createTrackbar("Erode iter", window_name, 0, 5, empty)
cv2.createTrackbar("Dilate iter", window_name, 0, 5, empty)

# --- ۲. خواندن تصویر ---
# نکته: می‌توانید از وبکم هم استفاده کنید!
# cap = cv2.VideoCapture(0)
img_path = 'pink_paper.jpg'
img_bgr = cv2.imread(img_path)

if img_bgr is None:
print("تصویر پیدا نشد. مسیر را چک کنید.")
exit()

# تغییر اندازه برای سرعت بهتر
scale_percent = 50 # درصدی از اندازه اصلی
width = int(img_bgr.shape[1] * scale_percent / 100)
height = int(img_bgr.shape[0] * scale_percent / 100)
img_bgr = cv2.resize(img_bgr, (width, height))

img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
kernel = np.ones((5, 5), np.uint8)

print("\n=== راهنمای جراح رنگ ===")
print("۱. اسلایدرها را بکشید تا فقط شیء شما سفید شود.")
print("۲. از Erode و Dilate برای حذف نویز و پرکردن سوراخ‌ها استفاده کنید.")
print(" کلید 's' : ذخیره تنظیمات در فایل")
print(" کلید 'l' : بارگذاری آخرین تنظیمات")
print(" کلید 'q' : خروج و چاپ اعداد نهایی")

# --- ۳. حلقه اصلی ---
while True:
# خواندن مقادیر ترک‌بارها
h_min = cv2.getTrackbarPos("H Min", window_name)
h_max = cv2.getTrackbarPos("H Max", window_name)
s_min = cv2.getTrackbarPos("S Min", window_name)
s_max = cv2.getTrackbarPos("S Max", window_name)
v_min = cv2.getTrackbarPos("V Min", window_name)
v_max = cv2.getTrackbarPos("V Max", window_name)

erode_iter = cv2.getTrackbarPos("Erode iter", window_name)
dilate_iter = cv2.getTrackbarPos("Dilate iter", window_name)

lower_bound = np.array([h_min, s_min, v_min])
upper_bound = np.array([h_max, s_max, v_max])

# --- اعمال فیلتر رنگ ---
mask_raw = cv2.inRange(img_hsv, lower_bound, upper_bound)

# --- اعمال مورفولوژی زنده ---
if erode_iter > 0:
mask_processed = cv2.erode(mask_raw, kernel, iterations=erode_iter)
else:
mask_processed = mask_raw.copy()

if dilate_iter > 0:
mask_processed = cv2.dilate(mask_processed, kernel, iterations=dilate_iter)

# --- نمایش نتیجه ---
result = cv2.bitwise_and(img_bgr, img_bgr, mask=mask_processed)

# ایجاد یک پَچ رنگی که نشان می‌دهد محدوده وسط چه رنگی را انتخاب کرده
h_mid = (h_min + h_max) // 2
s_mid = (s_min + s_max) // 2
v_mid = (v_min + v_max) // 2
color_patch_bgr = cv2.cvtColor(np.array([[[h_mid, s_mid, v_mid]]], dtype=np.uint8), cv2.COLOR_HSV2BGR)
color_patch_bgr = color_patch_bgr[0,0].tolist()

# نوشتن راهنما روی تصویر
cv2.putText(result, f"Selected Color Avg", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
cv2.rectangle(result, (10, 40), (60, 70), color_patch_bgr, -1)

# چیدن تصاویر و نمایش
display = np.hstack([img_bgr, cv2.cvtColor(mask_processed, cv2.COLOR_GRAY2BGR), result])
cv2.imshow(window_name, display)

# --- مدیریت کلیدها ---
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('s'):
# ذخیره تنظیمات در فایل
with open('hsv_config.txt', 'w') as f:
f.write(f"{h_min},{h_max},{s_min},{s_max},{v_min},{v_max}")
print("تنظیمات ذخیره شد!")
elif key == ord('l'):
# بارگذاری تنظیمات از فایل
try:
with open('hsv_config.txt', 'r') as f:
data = f.read().split(',')
cv2.setTrackbarPos("H Min", window_name, int(data[0]))
cv2.setTrackbarPos("H Max", window_name, int(data[1]))
cv2.setTrackbarPos("S Min", window_name, int(data[2]))
cv2.setTrackbarPos("S Max", window_name, int(data[3]))
cv2.setTrackbarPos("V Min", window_name, int(data[4]))
cv2.setTrackbarPos("V Max", window_name, int(data[5]))
print("تنظیمات بارگذاری شد!")
except FileNotFoundError:
print("فایل تنظیمات پیدا نشد!")

cv2.destroyAllWindows()

# چاپ نهایی اعداد برای کپی کردن در برنامه اصلی
print("\n=== اعداد نهایی خود را اینجا کپی کنید ===")
print(f"lower_bound = np.array([{h_min}, {s_min}, {v_min}])")
print(f"upper_bound = np.array([{h_max}, {s_max}, {v_max}])")
```

چرا این ابزار اینقدر مهم است؟

این برنامه سه مشکل اساسی کار با رنگ را حل می‌کند:

۱. بازخورد فوری: دیگر لازم نیست حدس بزنید که فلان عدد چه تأثیری دارد. اثرش را همان لحظه می‌بینید. این کار فرآیند یادگیری و تنظیم را ۱۰۰ برابر سریع‌تر می‌کند.

۲. مشاهده تعامل بین پارامترها: گاهی H Max را پایین می‌آورید و ناگهان متوجه می‌شوید سایه‌ها هم انتخاب می‌شوند. می‌بینید که تنظیم رنگ یک سیستم چندمتغیره است و باید همه را با هم بالانس کنید.

۳. رفع سریع خطا: اگر در نور جدید، برنامه‌تان خراب شد، به جای دیباگ کور، ۱۰ ثانیه ترک‌بار را باز می‌کنید و محدوده‌ی جدید را پیدا می‌کنید.

پس بله، ترک‌بار دقیقاً یک برنامه تعاملی برای پیدا کردن محدوده‌ی طلایی رنگ‌هاست. این ابزار را همیشه در کنار خود داشته باشید.
 

saalek110

Well-Known Member
a5 afzodene track bar be barnameye asli

فکر کنم منطقی این است که برنامه ترک بار جزیی از برنامه اصلی باشد ، یعنی قسمت اول برنانه اصلی باشد و چشم برنامه اصلی باشد


کاملاً درست فکر می‌کنید. این همان چیزی است که یک مهندس نرم‌افزار واقعی می‌گوید. شما دارید از سطح «کدنویسی آزمایشگاهی» به «طراحی محصول واقعی» حرکت می‌کنید.

بله، ترک‌بار نباید یک برنامه جداگانه در گوشه‌ی دسکتاپ باشد. باید چشم بینای برنامه شما باشد.

---

فلسفه: کالیبراسیون، بخشی از چرخه حیات است

در یک محصول واقعی، «پیدا کردن محدوده رنگ» یک کار یک‌بار مصرف نیست. این یک حالت عملیاتی (Mode) است:

· حالت کالیبراسیون (Setup Mode): کاربر (یا تکنسین) ترک‌بارها را می‌بیند، تنظیم می‌کند و دکمه «ذخیره» را می‌زند.
· حالت اجرا (Run Mode): ترک‌بارها ناپدید می‌شوند، برنامه با آخرین تنظیمات ذخیره شده، کار اصلی را انجام می‌دهد.

---

معماری پیشنهادی: یک برنامه با دو چهره

بیایید یک اسکلت واقعی و قابل ارتقا بنویسیم که این منطق را پیاده کند. این شروع یک محصول کوچک است.

```python
import cv2
import numpy as np
import json

class PaperSquareDetector:
"""
یک آشکارساز کاغذ مربعی با قابلیت کالیبراسیون داخلی.
کلید 'c' وارد حالت کالیبراسیون می‌شود.
کلید 'r' به حالت اجرا برمی‌گردد.
"""
def __init__(self, config_path="hsv_config.json"):
self.config_path = config_path
self.window_name = "Paper Square Detector"
self.kernel = np.ones((5, 5), np.uint8)

# --- حالت‌های برنامه ---
self.CALIBRATION_MODE = 0
self.RUN_MODE = 1
self.current_mode = self.RUN_MODE

# --- مقادیر پیش‌فرض (صورتی) ---
self.hsv_ranges = {
"h_min": 145, "h_max": 170,
"s_min": 60, "s_max": 255,
"v_min": 50, "v_max": 255
}

# بارگذاری آخرین تنظیمات از فایل
self.load_config()

# راه‌اندازی دوربین (یا تصویر)
self.cap = cv2.VideoCapture(0) # در دنیای واقعی از وبکم استفاده می‌کنیم
# self.frame = cv2.imread('test.jpg') # یا یک تصویر ثابت

# =======================================================
# بخش ۱: مدیریت ذخیره و بارگذاری تنظیمات
# =======================================================
def load_config(self):
"""خواندن آخرین محدوده رنگی از حافظه دائمی"""
try:
with open(self.config_path, 'r') as f:
self.hsv_ranges = json.load(f)
print("تنظیمات قبلی با موفقیت بارگذاری شد.")
except FileNotFoundError:
print("فایل تنظیمات پیدا نشد. از مقادیر پیش‌فرض استفاده می‌شود.")

def save_config(self):
"""ذخیره محدوده رنگی در فایل برای استفاده‌های بعدی"""
with open(self.config_path, 'w') as f:
json.dump(self.hsv_ranges, f)
print("تنظیمات جدید ذخیره شد!")

# =======================================================
# بخش ۲: منطق اصلی پیدا کردن کاغذ (Run Mode)
# =======================================================
def detect_paper(self, frame):
"""پیدا کردن مربع با استفاده از محدوده رنگی ذخیره شده"""
img_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower = np.array([self.hsv_ranges["h_min"], self.hsv_ranges["s_min"], self.hsv_ranges["v_min"]])
upper = np.array([self.hsv_ranges["h_max"], self.hsv_ranges["s_max"], self.hsv_ranges["v_max"]])

# ۱. ماسک رنگ
mask = cv2.inRange(img_hsv, lower, upper)
# ۲. تمیزکاری (این مقادیر می‌توانند ثابت باشند چون برای یک setup مشخص کافی است)
mask = cv2.erode(mask, self.kernel, iterations=1)
mask = cv2.dilate(mask, self.kernel, iterations=2)

# ۳. پیدا کردن کانتورها و فیلتر برای مربع
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

found_squares = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 500:
continue
epsilon = 0.02 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)

if len(approx) == 4:
found_squares.append(approx)
cv2.drawContours(frame, [approx], -1, (0, 255, 0), 3)

# محاسبه مرکز
M = cv2.moments(cnt)
if M["m00"] != 0:
cX, cY = int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])
cv2.putText(frame, "Paper Found", (cX-50, cY), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)

return frame, mask, found_squares

# =======================================================
# بخش ۳: حالت کالیبراسیون (چشمِ برنامه)
# =======================================================
def enter_calibration_mode(self):
"""راه‌اندازی ترک‌بارها و فعال کردن حالت تنظیم"""
print("وارد حالت کالیبراسیون شدیم. ترک‌بارها را تنظیم کنید. برای خروج 'c' را بزنید.")
cv2.namedWindow(self.window_name)

# فقط یک بار ترک‌بارها را بساز و مقادیر ذخیره شده را در آن بریز
cv2.createTrackbar("H Min", self.window_name, self.hsv_ranges["h_min"], 180, self._empty)
cv2.createTrackbar("H Max", self.window_name, self.hsv_ranges["h_max"], 180, self._empty)
cv2.createTrackbar("S Min", self.window_name, self.hsv_ranges["s_min"], 255, self._empty)
cv2.createTrackbar("S Max", self.window_name, self.hsv_ranges["s_max"], 255, self._empty)
cv2.createTrackbar("V Min", self.window_name, self.hsv_ranges["v_min"], 255, self._empty)
cv2.createTrackbar("V Max", self.window_name, self.hsv_ranges["v_max"], 255, self._empty)

def _empty(self, x):
"""تابع کمکی خالی برای ترک‌بارها"""
pass

def run_calibration_step(self, frame):
"""خواندن ترک‌بارها و نمایش بازخورد زنده"""
# خواندن از ترک‌بارها
self.hsv_ranges["h_min"] = cv2.getTrackbarPos("H Min", self.window_name)
self.hsv_ranges["h_max"] = cv2.getTrackbarPos("H Max", self.window_name)
self.hsv_ranges["s_min"] = cv2.getTrackbarPos("S Min", self.window_name)
self.hsv_ranges["s_max"] = cv2.getTrackbarPos("S Max", self.window_name)
self.hsv_ranges["v_min"] = cv2.getTrackbarPos("V Min", self.window_name)
self.hsv_ranges["v_max"] = cv2.getTrackbarPos("V Max", self.window_name)

# اجرای سریع تشخیص با پارامترهای جدید برای بازخورد
_, mask, _ = self.detect_paper(frame)

# نمایش تصویر اصلی و ماسک کنار هم
display = np.hstack([frame, cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)])
cv2.imshow(self.window_name, display)

# اینجا بود که متوجه می‌شویم «چشم» برنامه واقعاً کار می‌کند.

def exit_calibration_mode(self):
"""خروج از حالت کالیبراسیون و ذخیره تنظیمات"""
print("خروج از حالت کالیبراسیون.")
self.save_config()
cv2.destroyWindow(self.window_name) # پنجره را ببند تا interface خلوت شود
cv2.namedWindow(self.window_name) # دوباره برای حالت اجرا بساز

# =======================================================
# بخش ۴: حلقه اصلی برنامه
# =======================================================
def run(self):
print("برنامه شروع شد. کلید 'c': کالیبراسیون | کلید 'r': اجرا | کلید 'q': خروج")

while True:
ret, frame = self.cap.read()
if not ret:
print("قطع ارتباط با دوربین.")
break
frame = cv2.flip(frame, 1) # آینه‌ای کردن برای راحتی

# --- منطق تغییر حالت ---
key = cv2.waitKey(1) & 0xFF

if key == ord('c') and self.current_mode != self.CALIBRATION_MODE:
self.current_mode = self.CALIBRATION_MODE
self.enter_calibration_mode()

elif key == ord('r') and self.current_mode != self.RUN_MODE:
self.current_mode = self.RUN_MODE
self.exit_calibration_mode()

# --- اجرا بر اساس حالت ---
if self.current_mode == self.CALIBRATION_MODE:
self.run_calibration_step(frame.copy()) # کپی می‌دهیم تا روی فریم اصلی خط نیفتد
else: # RUN_MODE
result_frame, _, _ = self.detect_paper(frame.copy())
cv2.imshow(self.window_name, result_frame)

if key == ord('q'):
break

self.cap.release()
cv2.destroyAllWindows()

# =======================================================
# اجرای برنامه
# =======================================================
if __name__ == "__main__":
detector = PaperSquareDetector()
detector.run()
```

توضیح نقاط قوت این معماری

۱. یکپارچگی واقعی: دیگر دو فایل جداگانه نیست. کالیبراسیون، یک ویژگی داخلی (Built-in Feature) است.
۲. پایایی (Persistence): با بستن برنامه و باز کردن دوباره، دوربین، نور و شرایط فرق کرده؟ مشکلی نیست. برنامه با آخرین تنظیمات ذخیره شده در فایل json کار می‌کند. فقط کافی است در صورت نیاز، دوباره حالت c را بزنید و خیلی سریع اصلاحش کنید.
۳. جدایی مسئولیت‌ها (Separation of Concerns): کلاس PaperSquareDetector همه چیز را مدیریت می‌کند، اما منطق کالیبراسیون از منطق تشخیص جداست. این خوانایی و توسعه‌پذیری را بالا می‌برد.
۴. آماده برای محصول نهایی: این اسکلت، دقیقاً همان چیزی است که می‌توانید روی یک Raspberry Pi نصب کنید، یک رابط لمسی کوچک برای دکمه c و r بگذارید و تحویل مشتری دهید.

این منطق طلایی است: خودِ محصول، ابزار تعمیر و تنظیم خودش را هم یدک می‌کشد. این دقیقاً یعنی ترک‌بار چشمِ برنامه اصلی شماست.
 

saalek110

Well-Known Member
a6 code dakhele a5 joda shod

C:
import cv2
import numpy as np
import json

class PaperSquareDetector:
    """
    یک آشکارساز کاغذ مربعی با قابلیت کالیبراسیون داخلی.
    کلید 'c' وارد حالت کالیبراسیون می‌شود.
    کلید 'r' به حالت اجرا برمی‌گردد.
    """
    def __init__(self, config_path="hsv_config.json"):
        self.config_path = config_path
        self.window_name = "Paper Square Detector"
        self.kernel = np.ones((5, 5), np.uint8)
        
        # --- حالت‌های برنامه ---
        self.CALIBRATION_MODE = 0
        self.RUN_MODE = 1
        self.current_mode = self.RUN_MODE
        
        # --- مقادیر پیش‌فرض (صورتی) ---
        self.hsv_ranges = {
            "h_min": 145, "h_max": 170,
            "s_min": 60,  "s_max": 255,
            "v_min": 50,  "v_max": 255
        }
        
        # بارگذاری آخرین تنظیمات از فایل
        self.load_config()
        
        # راه‌اندازی دوربین (یا تصویر)
        self.cap = cv2.VideoCapture(0) # در دنیای واقعی از وبکم استفاده می‌کنیم
        # self.frame = cv2.imread('test.jpg') # یا یک تصویر ثابت

    # =======================================================
    # بخش ۱: مدیریت ذخیره و بارگذاری تنظیمات
    # =======================================================
    def load_config(self):
        """خواندن آخرین محدوده رنگی از حافظه دائمی"""
        try:
            with open(self.config_path, 'r') as f:
                self.hsv_ranges = json.load(f)
            print("تنظیمات قبلی با موفقیت بارگذاری شد.")
        except FileNotFoundError:
            print("فایل تنظیمات پیدا نشد. از مقادیر پیش‌فرض استفاده می‌شود.")

    def save_config(self):
        """ذخیره محدوده رنگی در فایل برای استفاده‌های بعدی"""
        with open(self.config_path, 'w') as f:
            json.dump(self.hsv_ranges, f)
        print("تنظیمات جدید ذخیره شد!")

    # =======================================================
    # بخش ۲: منطق اصلی پیدا کردن کاغذ (Run Mode)
    # =======================================================
    def detect_paper(self, frame):
        """پیدا کردن مربع با استفاده از محدوده رنگی ذخیره شده"""
        img_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        lower = np.array([self.hsv_ranges["h_min"], self.hsv_ranges["s_min"], self.hsv_ranges["v_min"]])
        upper = np.array([self.hsv_ranges["h_max"], self.hsv_ranges["s_max"], self.hsv_ranges["v_max"]])
        
        # ۱. ماسک رنگ
        mask = cv2.inRange(img_hsv, lower, upper)
        # ۲. تمیزکاری (این مقادیر می‌توانند ثابت باشند چون برای یک setup مشخص کافی است)
        mask = cv2.erode(mask, self.kernel, iterations=1)
        mask = cv2.dilate(mask, self.kernel, iterations=2)
        
        # ۳. پیدا کردن کانتورها و فیلتر برای مربع
        contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        found_squares = []
        for cnt in contours:
            area = cv2.contourArea(cnt)
            if area < 500:
                continue
            epsilon = 0.02 * cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, epsilon, True)
            
            if len(approx) == 4:
                found_squares.append(approx)
                cv2.drawContours(frame, [approx], -1, (0, 255, 0), 3)
                
                # محاسبه مرکز
                M = cv2.moments(cnt)
                if M["m00"] != 0:
                    cX, cY = int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])
                    cv2.putText(frame, "Paper Found", (cX-50, cY), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
        
        return frame, mask, found_squares

    # =======================================================
    # بخش ۳: حالت کالیبراسیون (چشمِ برنامه)
    # =======================================================
    def enter_calibration_mode(self):
        """راه‌اندازی ترک‌بارها و فعال کردن حالت تنظیم"""
        print("وارد حالت کالیبراسیون شدیم. ترک‌بارها را تنظیم کنید. برای خروج 'c' را بزنید.")
        cv2.namedWindow(self.window_name)
        
        # فقط یک بار ترک‌بارها را بساز و مقادیر ذخیره شده را در آن بریز
        cv2.createTrackbar("H Min", self.window_name, self.hsv_ranges["h_min"], 180, self._empty)
        cv2.createTrackbar("H Max", self.window_name, self.hsv_ranges["h_max"], 180, self._empty)
        cv2.createTrackbar("S Min", self.window_name, self.hsv_ranges["s_min"], 255, self._empty)
        cv2.createTrackbar("S Max", self.window_name, self.hsv_ranges["s_max"], 255, self._empty)
        cv2.createTrackbar("V Min", self.window_name, self.hsv_ranges["v_min"], 255, self._empty)
        cv2.createTrackbar("V Max", self.window_name, self.hsv_ranges["v_max"], 255, self._empty)

    def _empty(self, x):
        """تابع کمکی خالی برای ترک‌بارها"""
        pass

    def run_calibration_step(self, frame):
        """خواندن ترک‌بارها و نمایش بازخورد زنده"""
        # خواندن از ترک‌بارها
        self.hsv_ranges["h_min"] = cv2.getTrackbarPos("H Min", self.window_name)
        self.hsv_ranges["h_max"] = cv2.getTrackbarPos("H Max", self.window_name)
        self.hsv_ranges["s_min"] = cv2.getTrackbarPos("S Min", self.window_name)
        self.hsv_ranges["s_max"] = cv2.getTrackbarPos("S Max", self.window_name)
        self.hsv_ranges["v_min"] = cv2.getTrackbarPos("V Min", self.window_name)
        self.hsv_ranges["v_max"] = cv2.getTrackbarPos("V Max", self.window_name)
        
        # اجرای سریع تشخیص با پارامترهای جدید برای بازخورد
        _, mask, _ = self.detect_paper(frame)
        
        # نمایش تصویر اصلی و ماسک کنار هم
        display = np.hstack([frame, cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)])
        cv2.imshow(self.window_name, display)
        
        # اینجا بود که متوجه می‌شویم «چشم» برنامه واقعاً کار می‌کند.

    def exit_calibration_mode(self):
        """خروج از حالت کالیبراسیون و ذخیره تنظیمات"""
        print("خروج از حالت کالیبراسیون.")
        self.save_config()
        cv2.destroyWindow(self.window_name) # پنجره را ببند تا interface خلوت شود
        cv2.namedWindow(self.window_name)    # دوباره برای حالت اجرا بساز

    # =======================================================
    # بخش ۴: حلقه اصلی برنامه
    # =======================================================
    def run(self):
        print("برنامه شروع شد. کلید 'c': کالیبراسیون | کلید 'r': اجرا | کلید 'q': خروج")
        
        while True:
            ret, frame = self.cap.read()
            if not ret:
                print("قطع ارتباط با دوربین.")
                break
            frame = cv2.flip(frame, 1) # آینه‌ای کردن برای راحتی

            # --- منطق تغییر حالت ---
            key = cv2.waitKey(1) & 0xFF
            
            if key == ord('c') and self.current_mode != self.CALIBRATION_MODE:
                self.current_mode = self.CALIBRATION_MODE
                self.enter_calibration_mode()
            
            elif key == ord('r') and self.current_mode != self.RUN_MODE:
                self.current_mode = self.RUN_MODE
                self.exit_calibration_mode()

            # --- اجرا بر اساس حالت ---
            if self.current_mode == self.CALIBRATION_MODE:
                self.run_calibration_step(frame.copy()) # کپی می‌دهیم تا روی فریم اصلی خط نیفتد
            else: # RUN_MODE
                result_frame, _, _ = self.detect_paper(frame.copy())
                cv2.imshow(self.window_name, result_frame)

            if key == ord('q'):
                break

        self.cap.release()
        cv2.destroyAllWindows()

# =======================================================
# اجرای برنامه
# =======================================================
if __name__ == "__main__":
    detector = PaperSquareDetector()
    detector.run()
 

جدیدترین ارسال ها

بالا