پردازش تصویر با OpenCV – رفع قرمزی چشم (به همراه کد پایتون و ++C)


در این نوشته قصد داریم در مورد چگونگی رفع خودکار قرمزی چشمها در یک عکس توضیحاتی ارائه دهیم. همچنین نمونه کدهای انجام این کار در زبانهای پایتون و ++C ارائه شده است.
از زمان پیدایش دوربینهای عکاسی با فیلم رنگی، مسئله از بین بردن قرمزی چشم یکی از قدیمیترین آرزوهای همه کسانی بوده است که با این دوربینها به عکاسی از سوژههای انسانی میپرداختند. در آغاز معرفی دوربینهای عکاسی با فیلمهای رنگی، هم هزینه تهیه دوربین بالا بود و هم فیلمهای آن گران قیمت بودند. در آن دوره دوربینهای عکاسی فوری وجود داشتند که به مدت دههها بر بازار دوربین های عکاسی حاکم بودند. از آنجایی که این دوربینها وسایل گرانقیمتی محسوب میشدند، خانوادهها با آنها همانند جواهرات گرانقیمت برخورد میکردند. از طرفی چون هزینه تهیه فیلم بسیار زیاد بود، عکاسی تنها در مواردی بسیار خاص انجام میشد.
گاهی اوقات، ماهها طول میکشید تا عکسهایی که گرفته شده بودند ظاهر شوند. در موارد زیادی عکسهایی که در شب گرفته شده بودند با مشکل اثر قرمزی چشم، مواجه میشدند. در چنین عکسهایی لبخند زدن افراد با چشمهای خونین، انسان را به یاد دراکولا میانداخت.
تا حل کامل این مشکل باید تا ورود به عصر دیجیتال صبر میشد. با معرفی فناوریهای مرتبط با عکاسی دیجیتال برخی ابزارهای ویرایش عکس برای رفع قرمزی چشمها توسعه یافتند. سادگی و سهولت این ابزارها همه عکاسان را شگفتزده کرد. در این نوشته قصد داریم برخی اصول و چگونگی عملکرد این نرم افزارها را توضیح دهیم. ساخت یک اپلیکیشن قدرتمند رفع قرمزی چشم که برای انواع عکسها کارساز باشد، فراتر از حوصله این نوشته است. با این وجود، ما اصول اساسی را آموزش میدهیم.
علت قرمزی چشم هنگام عکاسی با فلاش
هنگامی که در یک اتاق تاریک هستید، مردمکهای چشم برای ورود نور بیشتر و در نتیجه کمک به دید بهتر در حالت انبساط (گشاد شدن) هستند. فلاش اکثر دوربینها بسیار نزدیک به لنز است. هنگامی که عکسی را با فلاش روشن میگیرید، نور فلاش از طریق مردمک گشادشده به پشت کره چشم میرود و از طریق مردمک به لنز دوربین بازتاب می یابد. قسمت انتهایی کره چشم، شبکیه نامیده میشود. دلیل رنگ قرمز، هجوم خون زیاد به شبکیه چشم است.
تصویر فوق نمایی از شبکیه چشم را نشان میدهد. بررسی شبکیه چشم اطلاعات زیادی در مورد سلامتی فرد ارائه میدهد. حتی برنامههایی برای گوشیهای هوشمند عرضه شدهاند که میتوان تصویر شبکیه را به وسیله یک قطعه سختافزاری مشاهده کرد. امروزه اکثر فلاشهای دوربینها طوری طراحی شدهاند که برای چند ثانیه به صورت متناوب چشمک میزنند. این کار باعث میشود مردمک چشم منقبض شود و بنابراین احتمال قرمزی چشم کاهش مییابد.
رفع خودکار قرمزی چشم
در این بخش، ما الگوریتم مورد استفاده برای حذف خودکار قرمزی چشم را به صورت گام به گام بررسی خواهیم کرد.
گام 1: شناسایی چشم
گام اول این است که چشمها به صورت خودکار شناسایی شوند. برای تشخیص و پیدا کردن چشمها از شناساگر استاندارد OpenCV Haar (فایل haarcascade_eye.xml) استفاده میکنیم. گاهی اوقات لازم است که ابتدا یک شناسایی چهره اجرا شود و سپس چشمها در داخل ناحیه چهره شناسایی شوند. در این نوشته برای سادگی کار، شناسایی چشم را به طور مستقیم بر روی عکس انجام میدهیم. حذف مرحله شناسایی چهره تنها زمانی ممکن است که تصویر ورودی، یک عکس پرتره باشد، یا بخواهید عکس نزدیکی از چشمها بگیرید.
همچنین میتوانید الگوریتم شناسایی اشیای HAAR را با استفاده از این دستورالعمل تمرین بدهید. کد شناسایی چشم در ادامه ارائه شده است.
C++
// Read image Mat img = imread("red_eyes.jpg",CV_LOAD_IMAGE_COLOR); // Output image Mat imgOut = img.clone(); // Load HAAR cascade CascadeClassifier eyes_cascade("haarcascade_eye.xml"); // A vector of Rect for storing bounding boxes for eyes. std::vector<Rect> eyes; // Detect eyes. eyesCascade.detectMultiScale( img, eyes, 1.3, 4, 0 | CASCADE_SCALE_IMAGE, Size(100, 100) );
Python
# Read image img = cv2.imread("red_eyes.jpg", cv2.IMREAD_COLOR) # Output image imgOut = img.copy() # Load HAAR cascade eyesCascade = cv2.CascadeClassifier("haarcascade_eye.xml") # Detect eyes eyes = eyesCascade.detectMultiScale(img,scaleFactor=1.3, minNeighbors=4, minSize=(100, 100))
گام 2: ماسک کردن چشمهای قرمز
در این مرحله، باید بخشی از مردمک که تحت تاثیر قرمزی چشمها قرار گرفته است را پیدا کنیم. راههای مختلفی برای پیدا کردن رنگ قرمز وجود دارد. یکی از نکاتی که باید به آن توجه داشته باشید، این است که رنگی که به دنبالش هستیم، در واقع قرمز خالص نیست، بلکه قرمز روشن است. شما میتوانید تصویر را به فضای رنگی HSV تبدیل کنید و بر اساس طیف رنگ و میزان روشنایی آن، آستانه (threshold) مطلوب را تعیین کنید.
در این نوشته از یک روش ابتکاری ساده استفاده شده است. ما کانال قرمز را این طور تعریف می کنیم که باید از یک حد آستانه بیشتر باشد و همچنین مجموع کانالهای سبز و آبی باشد. برای این نوشته که قصد دارد کارآمدی سیستم را اثبات کند، این ابتکار کافی است. اما چنانچه بخواهید یک نرمافزار تجاری برای کاهش قرمزی چشم به صورت خودکار توسعه دهید، باید هزاران تصویر دارای قرمزی چشم را گردآوری کنید تا به نتیجه بهتری برسید.
در کد زیر، ما روی تمام نواحی چشم که در مرحله قبل شناسایی شده است یک حلقه را به صورت تکراری اجرا می کنیم. سپس رنگ تصویر را با استفاده از دستور split به سه کانال رنگی مجزا تقسیم کردیم. در نهایت یک ماسک ایجاد میکنیم که مقدار آن برای هر پیکسل که کانال قرمز آن بالاتر از آستانه (150) تعریف شده و بزرگتر از مجموع کانالهای سبز و آبی باشد، برابر با 1 است.
C++
for( size_t i = 0; i < eyes.size(); i++ ) { // Extract eye from the image. Mat eye = img(eyes[i]); // Split eye image into 3 channels. vector<Mat>bgr(3); split(eye,bgr); // Simple red eye detector Mat mask = (bgr[2] > 150) & (bgr[2] > ( bgr[1] + bgr[0] )); }
Python
for (x, y, w, h) in eyes: # Extract eye from the image. eye = img[y:y+h, x:x+w] # Split eye image into 3 channels b = eye[:, :, 0] g = eye[:, :, 1] r = eye[:, :, 2] # Add the green and blue channels. bg = cv2.add(b, g) # Simple red eye detector mask = (r > 150) & (r > bg) # Convert the mask to uint8 format. mask = mask.astype(np.uint8)*255
گام 3: پاک کردن ماسک مردمک

ماسک ایجاد شده در مرحله قبل به احتمال زیاد حفره خواهد داشت. تصویر سمت چپ شکل بالا نشانگر ماسک خام حاصل از پردازش رنگ است. حفره ماسک را با استفاده از کد زیر رفع میکنیم. برای اطلاعات بیشتر در مورد چگونگی کارکرد این کد میتوانید نوشتههای آتی بلاگ فرادرس در مورد پر کردن حفرههای ماسک را دنبال کنید.
C++
void fillHoles(Mat &mask) { Mat mask_floodfill = mask.clone(); floodFill(mask_floodfill, cv::Point(0,0), Scalar(255)); Mat mask2; bitwise_not(mask_floodfill, mask2); mask = (mask2 | mask); }
پایتون
def fillHoles(mask): maskFloodfill = mask.copy() h, w = maskFloodfill.shape[:2] maskTemp = np.zeros((h+2, w+2), np.uint8) cv2.floodFill(maskFloodfill, maskTemp, (0, 0), 255) mask2 = cv2.bitwise_not(maskFloodfill) return mask2 | mask
علاوه بر این، بسط دادن ماسک مردمک ایده خوبی است، چون ناحیهای بزرگتر از حد لازم را پوشش میدهد. دلیل این امر آن است که در لبههای مرزی، رنگ قرمز به تدریج محو می شود و ممکن است برخی از نواحی قرمز در ماسک اصلی را شامل نشود. در تصویر بالا، در سمت راست، ماسک بسط داده شده است. با استفاده از کد زیر می توان ماسک ایجاد شده را بسط داد.
C++
// Clean up mask by filling holes and dilating fillHoles(mask); dilate(mask, mask, Mat(), Point(-1, -1), 3, 1, 1);
پایتون
1# Clean up mask by filling holes and dilating
2mask = fillHoles(mask)
3mask = cv2.dilate(mask, None, anchor=(-1, -1), iterations=3, borderType=1, borderValue=1)
گام 4: رفع قرمزی چشمها
حالا ما یک ماسک داریم که صرفاً ناحیه قرمز هر چشم را شامل میشود. در ادامه چگونگی پردازش فضای داخل این ماسک برای رفع قرمزی چشمها را توضیح میدهیم. میدانیم که قرمزی چشم ها باعث می شود کانال قرمز در تصویر اشباع شود. به عبارت دیگر، تمام اطلاعات کانال قرمز از بین رفتهاند. چطور میتوانیم برخی از این اطلاعات را بازگردانیم؟ در زمان رفع قرمزی چشمها لازم نیست که بافت واقعی کانالهای قرمز را بازیابی کنیم؛ کافی است فقط یک بافت قابل قبول پیدا کنیم.
خوشبختانه اثر قرمزی چشم تنها بافت کانال قرمز را از بین میبرد. کانال آبی و سبز هنوز خوب هستند. این وضعیت را میتوانید در شکل زیر که در آن کانالهای قرمز، سبز و آبی تصویر نمایش داده شدهاند، مشاهده کنید.

برای اینکه یک کانال قرمز قابل قبول ساخته شود، میتوان ترکیبی از کانالهای سبز و آبی را مورد استفاده قرار داد. برای مثال، میتوانیم یک کانال قرمز که میانگین کانالهای سبز و آبی است، ایجاد کنیم. با این حال، انجام این کار میتواند مردمک را به صورت یک سایه کمرنگ ارائه کند که که گرچه ممکن است خوب به نظر برسد، اما عالی محسوب نمی شود. در تصویر زیر به رنگ بنفش در مردمک وسط توجه کنید.

رنگ مردمک پس از رفع قرمزی
بدین ترتیب یک سوال مهم پیش می آید. مردمک باید چه رنگی داشته باشد؟ مردمک یک حفره خالی در چشم است و داخل چشم کاملا تاریک است. بنابراین مردمک باید بدون رنگ (سیاه و سفید) و تاریک باشد. بدین منظور به جای جایگزینی تنها کانال قرمز در ناحیه مردمک، تمام کانالها را با میانگین کانالهای سبز و آبی جایگزین میکنیم. این کار منجر به حذف رنگ بنفش میشود.
در کد زیر، ابتدا کانال میانگین از طریق معدلگیری کانالهای سبز و آبی، ایجاد میشود. سپس هر سه کانال در تمام پیکسلهای داخل ناحیه ماسک، با این کانال میانگین جایگزین میشوند.
C++
1// Calculate the mean channel by averaging
2// the green and blue channels
3Mat mean = (bgr[0]+bgr[1])/2;
4
5// Copy the mean image to blue channel with mask.
6mean.copyTo(bgr[0], mask);
7
8// Copy the mean image to green channel with mask.
9mean.copyTo(bgr[1], mask);
10
11// Copy the mean image to red channel with mask.
12mean.copyTo(bgr[2], mask);
Python
1# Calculate the mean channel by averaging
2# the green and blue channels. Recall, bg = cv2.add(b, g)
3mean = bg / 2
4mask = mask.astype(np.bool)[:, :, np.newaxis]
5mean = mean[:, :, np.newaxis]
6
7# Copy the eye from the original image.
8eyeOut = eye.copy()
9
10# Copy the mean image to the output image.
11np.copyto(eyeOut, mean, where=mask)
گام 5: جایگزینی ناحیه تصحیحشده چشم
در مرحله قبل ما سه کانال را تصحیح کردیم. آخرین مرحله این است که این سه کانال را برای ایجاد تصویر RGB ترکیب کنیم و سپس این ناحیه تصحیح شده چشم را در تصویر اصلی قرار دهیم.
C++
// Merge the three channels Mat eyeOut; merge(bgr,eyeOut); // Copy the fixed eye to the output image. eyeOut.copyTo(imgOut(eyes[i]));
Python
# Copy the fixed eye to the output image. imgOut[y:y+h, x:x+w, :] = eyeOut
نتایج رفع خودکار قرمزی چشم
در تصویر زیر نتایج استفاده از کدی که نوشتیم را بر روی عکس نمونهای که در ابتدای نوشته ارائه کردیم، مشاهده می کنید.
توجه داشته باشید که حذف تمام رنگ ناحیه مردمک باعث میشود تصویر زیبا به نظر برسد، چون نقطه مرکز چشم کاملا سفید است. همچنین توجه کنید که روی مرز مردمک، قرمزی به تدریج محو شده است اما به دلیل عملیات انبساط ماسک، این ناحیه را نیز مورد پردازش قرار داده ایم.
در ادامه نتیجه استفاده از کد بر روی یک عکس نزدیک از چشم ها نشان داده شده است. در این عکس اگر از شناساگر چهره استفاده میکردیم، چهرهای پیدا نمیشد، و شناساگر اتوماتیک چشم به درستی عمل نمیکرد.
امیدوار هستیم این آموزش مورد توجه شما واقع شده باشد. ما در فرادرس سلسله آموزشهایی در حوزه پردازش تصویر ارائه میکنیم که با دنبال کردن مطالب میتوانید از آنها هم بهرهمند شوید. هر گونه سوال یا نظری داشتید میتوانید در بخش نظرات در ادامه مطرح کنید.
سلام وقت بخیر جناب دکتر
اگر لطف کنید اموزش تصویر در حال حرکت هم به همراه کد پایتون. قرار دهید
ممنون از زحمات بی دریغ شما همیشه موفق باشید
اجرکم عند الله