داده افزایی تصویری و چگونگی انجام آن در کتابخانهی OpenCV
چند روز پیش در حال نوشتن مقالهای بودم که به استفاده از فضای رنگی Color spaces متفاوت به عنوان ورودی شبکه های عصبی پیچشی (CNN) میپرداخت و به همین دلیل لازم بود از یک مولد داده برای داده افزایی تصویری استفاده کنم؛ چون نمیتوانستم از مولد داده داخلی تنسورفلو بدین منظور استفاده کنم. بعد از جستوجو، چندین مقالهی مرتبط پیدا کردم، اما هیچ کدام آنها به طور کامل این موضوع را پوشش نداده بودند؛ بنابراین تصمیم گرفتم شخصاً این کار را انجام دهم.
در صورت تمایل به آشنایی با نحوهی کار هر کدام از این توابع میتوانید به این مقاله مراجعه کنید. برای مصورسازی از تصویر طاق پیروزی (تصویر اصلی آنرا پایین مشاهده میکنید) استفاده خواهیم کرد.
جابجایی افقی
در اولین بخش از بحث داده افزایی تصویری به جابجایی افقی خواهیم پرداخت. جابجایی یا برگردان translation افقی به جابهجا کردن یک تصویر به چپ یا راست بر اساس یک نسبت گفته میشود؛ این نسبت حداکثر میزان جابجایی را مشخص میکند. به منظور تنظیم مجدد تصویر و برگشت به ابعاد اصلی، Keras به صورت پیشفرض از یک مقدار ورودی به نام nearest استفاده میکند که قسمتهای از دست رفته عکس را پر میکند.
این عمل باعث میشود قسمتی از تصویر بلااستفاده باقی بماند، به همین دلیل تصویر را به اندازهی اصلی باز میگردانم. پس آموختیم که برای انجام این کار باید یک عدد تصادفی تولید کنیم و سپس تصویر را از طریق شاخصگذاری indexing به سمت چپ یا راست جابجا کنیم.
import cv2 import random img = cv2.imread('arc_de_triomphe.jpg') def fill(img, h, w): img = cv2.resize(img, (h, w), cv2.INTER_CUBIC) return img def horizontal_shift(img, ratio=0.0): if ratio > 1 or ratio < 0: print('Value should be less than 1 and greater than 0') return img ratio = random.uniform(-ratio, ratio) h, w = img.shape[:2] to_shift = w*ratio if ratio > 0: img = img[:, :int(w-to_shift), :] if ratio < 0: img = img[:, int(-1*to_shift):, :] img = fill(img, h, w) return img img = horizontal_shift(img, 0.7) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
جابجایی عمودی
دیگر بحثی که برای داده افزایی تصویری از اهمیت زیادی برخوردار است، جابجایی عمودی است. اجرای جابجایی عمودی تفاوت چندانی با نحوهی جابجایی افقی ندارد.
import cv2 import random img = cv2.imread('arc_de_triomphe.jpg') def fill(img, h, w): img = cv2.resize(img, (h, w), cv2.INTER_CUBIC) return img def vertical_shift(img, ratio=0.0): if ratio > 1 or ratio < 0: print('Value should be less than 1 and greater than 0') return img ratio = random.uniform(-ratio, ratio) h, w = img.shape[:2] to_shift = h*ratio if ratio > 0: img = img[:int(h-to_shift), :, :] if ratio < 0: img = img[int(-1*to_shift):, :, :] img = fill(img, h, w) return img img = vertical_shift(img, 0.7) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
روشنایی
روشنایی یکی از عوامل مهم در داده افزایی تصویری است. پیشتر مقالهای در مورد فیلترهای مختلف (کارتونی، تابستانی، زمستانی و …) تصاویر نوشته بودم که تابع روشنایی را در بر میگرفت.
برای انجام این مسئله باید از فضای رنگی HSV استفاده کنیم. هرچه مقدار اشباع رنگ Value of saturation و ماتریسهای مقادیر Value matrices بالاتر باشد، روشنایی هم بیشتر خواهد بود. بنابراین به منظور افزایش روشنایی باید این مقادیر را در عددی بزرگتر از 1 و برای کاهش آن در عددی کوچکتر از 1 ضرب کنیم. در کتابخانهی تنسورفلو ابتدا یک بازه مشخص شده و سپس از درون این بازه یک مقدار به صورت تصادفی انتخاب میگردد.
import cv2 import random import numpy as np img = cv2.imread('arc_de_triomphe.jpg') def brightness(img, low, high): value = random.uniform(low, high) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv = np.array(hsv, dtype = np.float64) hsv[:,:,1] = hsv[:,:,1]*value hsv[:,:,1][hsv[:,:,1]>255] = 255 hsv[:,:,2] = hsv[:,:,2]*value hsv[:,:,2][hsv[:,:,2]>255] = 255 hsv = np.array(hsv, dtype = np.uint8) img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) return img img = brightness(img, 0.5, 3) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
زوم
در کتابخانهی تنسورفلو برای اجرای تابع زوم ابتدا یک بازه را در نظر گرفته و از درون آن مقادیری به صورت تصادفی انتخاب میکنیم. اگر این مقادیر از 1 کوچکتر باشند، تصویر بزرگنمایی میشود و اگر بزرگتر از 1 باشند، تصویر کوچک شده و سپس با nearest به ابعاد واقعی خود برمیگردد. با این حال، از آنجایی که میخواهیم تصاویر را به اندازهی واقعی آنها برگردانیم، فقط مقادیر کمتر از 1 را انتخاب میکنیم. برای نمونه مقدار 0.6 بدین معنی است که 60% از کل تصویرگرفته خواهد شد و سپس تصویر به ابعاد اصلی بر خواهد گشت.
import cv2 import random img = cv2.imread('arc_de_triomphe.jpg') def fill(img, h, w): img = cv2.resize(img, (h, w), cv2.INTER_CUBIC) return imgdef zoom(img, value): if value > 1 or value < 0: print('Value for zoom should be less than 1 and greater than 0') return img value = random.uniform(value, 1) h, w = img.shape[:2] h_taken = int(value*h) w_taken = int(value*w) h_start = random.randint(0, h-h_taken) w_start = random.randint(0, w-w_taken) img = img[h_start:h_start+h_taken, w_start:w_start+w_taken, :] img = fill(img, h, w) return imgimg = zoom(img, 0.5) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
جابجایی کانالی
دیگر مورد لازم برای داده افزایی تصویری جابجایی کانالی است. برای اجرای جابجایی کانالی، یک مقدار را به صورت تصادفی از بازهای معین انتخاب کرده و به همهی کانالهای تصویر اضافه میکنیم. نتیجه تقریباً شبیه تابع روشنایی است:
import cv2 import random import numpy as np img = cv2.imread('arc_de_triomphe.jpg') def channel_shift(img, value): value = int(random.uniform(-value, value)) img = img + value img[:,:,:][img[:,:,:]>255] = 255 img[:,:,:][img[:,:,:]<0] = 0 img = img.astype(np.uint8) return img img = channel_shift(img, 60) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
چرخش افقی
برای اجرای این تابع چرخش افقی در مبحث داده افزایی تصویری به یک متغیر بولی Boolean variable نیاز داریم؛ این متغیر مشخص میکند یک چرخش افقی انجام میشود یا خیر. یک تابع داخلی در OpenCV (cv2.flip) وجود دارد که صرفاً برای اجرای این عملیات مورد استفاده قرار میگیرد.
import cv2 img = cv2.imread('arc_de_triomphe.jpg') def horizontal_flip(img, flag): if flag: return cv2.flip(img, 1) else: return img img = horizontal_flip(img, True) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
چرخش عمودی
این تابع نیز همچون تابع چرخش افقی با استفاده از cv2.flip برای داده افزایی تصویری انجام میشود. اما آرگومانargument دوم آن باید 0 باشد.
import cv2img = cv2.imread('arc_de_triomphe.jpg') def vertical_flip(img, flag): if flag: return cv2.flip(img, 0) else: return img img = vertical_flip(img, True) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
گردش
برای اجرای تابع گردش در بحث داده افزایی تصویری در OpenCV باید یک ماتریکس گردش ایجاد کنیم و سپس تبدیلات آفین Affine transformations را روی آن اعمال نماییم.
import cv2 img = cv2.imread('arc_de_triomphe.jpg') def rotation(img, angle): angle = int(random.uniform(-angle, angle)) h, w = img.shape[:2] M = cv2.getRotationMatrix2D((int(w/2), int(h/2)), angle, 1) img = cv2.warpAffine(img, M, (w, h)) return img img = rotation(img, 30) cv2.imshow('Result', img) cv2.waitKey(0) cv2.destroyAllWindows()
تمامصفحه
پیشتر از حالت تمامصفحهی nearest گفتیم و اشاره کردم که به جای استفاده از آن، اندازهی تصاویر را تغییر میدهم. با این حال از آنجایی که قصد دارم لیست کاملی را در این مقاله ارائه کرده باشم، نحوهی ساخت همهی حالتهای تمامصفحه را (اعم از nearest) توضیح خواهم داد. متن پایین از مستندات تنسورفلو گرفته شده است:
fill_mode: One of {“constant”, “nearest”, “reflect” or “wrap”}. Default is ‘nearest’. Points outside the boundaries of the input are filled according to the given mode:
‘constant’: kkkkkkkk|abcd|kkkkkkkk (cval=k)
‘nearest’: aaaaaaaa|abcd|dddddddd
‘reflect’: abcddcba|abcd|dcbaabcd
‘wrap’: abcdabcd|abcd|abcdabcd
این موارد را با جابجایی افقی درست میکنم. تابع cv2.copyMakeBorder در تمامصفحه کردن تصویر کاربرد دارد؛ این تابع آرگومانها را به عنوان وضعیت بالا، پایین، چپ، راست و حالت تصویر در نظر میگیرد. بالا، پایین، چپ و راست به اندازهی مرز تصویر اشاره دارند.
import cv2 from matplotlib import pyplot as plt img = cv2.imread('arc_de_triomphe.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) def fill_mode(img, left, right): nearest = cv2.copyMakeBorder(img, 0, 0, left, right, cv2.BORDER_REPLICATE) reflect = cv2.copyMakeBorder(img, 0, 0, left, right, cv2.BORDER_REFLECT) wrap = cv2.copyMakeBorder(img, 0, 0, left, right, cv2.BORDER_WRAP) constant= cv2.copyMakeBorder(img, 0, 0, left, right, cv2.BORDER_CONSTANT,value=(255, 0, 0)) plt.subplot(221),plt.imshow(nearest,'gray'),plt.title('NEAREST'),plt.axis('off') plt.subplot(222),plt.imshow(reflect,'gray'),plt.title('REFLECT'),plt.axis('off') plt.subplot(223),plt.imshow(wrap,'gray'),plt.title('WRAP'),plt.axis('off') plt.subplot(224),plt.imshow(constant,'gray'),plt.title('CONSTANT'),plt.axis('off') def horizontal_shift_mode(img, ratio): if ratio > 1 or ratio < 0: print('Value for horizontal shift should be less than 1 and greater than 0') return img ratio = random.uniform(-ratio, ratio) h, w = img.shape[:2] to_shift = int(w*ratio) if ratio > 0: img = img[:, :w-to_shift, :] fill_mode(img, to_shift, 0) if ratio < 0: img = img[:, -1*to_shift:, :] fill_mode(img, 0, -1*to_shift)horizontal_shift_mode(img, 0.8)
در صورت تمایل به آشنایی با نحوهی ایجاد یک مولد دیتای ساختگی، به این مقاله مراجعه کنید.