شما اینجا هستید

۳-۱ -- NumPy

پیغام خطا

Deprecated function: The each() function is deprecated. This message will be suppressed on further calls در book_prev() (خط 775 در /home/molavy/public_html/modules/book/book.module).

بسته NumPy که از آدرس زیر می توان آن را دریافت کرد

http://www.scipy.org/NumPy/

یک بسته رایج برای کار های علمی و تحقیقاتی است که در آنها از پایتون استفاده می شود. NumPy دربردارنده مفاهیم مفیدی مانند شی های آرایه (برای نمایش بردار ها، ماتریس ها، تصاویر و غیره) و توابع جبر خطی است. شی آرایه NumPy در تقریبا تمام مثال های این کتاب استفاده می شود. شی آرایه اجازه می دهد عملیات های مهمی مانند ضرب ماتریس ها، انتقال و تبدیل ماتریس، حل معادلات، ضرب برداری، و نرمال کردن را انجام دهیم.این عملیات ها برای مواردی مانند تراز کردن تصویر، تغییر تصویر، و مدل سازی از تغییرات، طبقه بندی تصاویر، گروه بندی تصاویر و از این قبیل استفاده می شوند.

مستندات NumPy به صورت آنلاین در آدرس

http://docs.scipy.org/doc/numpy

در دسترس است و در بردارنده پاسخ بیشتر سوالات است. برای جزییات بیشتر درباره NumPy کتاب های رایگانی در این باره منتشر شده اند که می توانید به آنها رجوع کنید.

 

نمایش آرایه ای تصویر(Array Image Representation)

وقتی ما تصاویر را در مثال های قبل بارگذاری می کردیم، آنها را با استفاده تابع array به شی آرایه NumPy تبدیل می کردیم، اما هیچگاه معنی آن را توضیح ندادیم.

نکته: بسته PyLab در خودش بعضی بخش های بسته NumPy را قرار داده است(مانند نوع آرایه). بنابراین ما می توانستیم از آن در مثال های بخش ۱-۲ استفاده کنیم.

آرایه ها در NumPy چند کاربردی هستند و می توانند نشان دهنده بردار، ماتریس و یا تصویر باشند. یک آرایه خیلی شبیه یک لیست است(یا لیستی از لیست ها است) اما تنها اجازه می دهد که تمام عناصر آن تنها از یک نوع باشند.اگر در هنگام ایجاد آرایه نوع آن را تعریف نکنیم نوع بر اساس داده ها به صورت خودکار تنظیم می شود.

مثال زیر نمایشی از ایجاد آرایه برای تصاویر است:

im = array(Image.open('empire.jpg'))

print im.shape, im.dtype

im = array(Image.open('empire.jpg').convert('L'),'f')

print im.shape, im.dtype

 

در خروجی چیزی شبیه به زیر را می بینیم

(800, 569, 3) uint8

(800, 569) float32

تاپل اول در هر خط شکل آرایه تصویر را نشان می دهد

(کانال رنگ, تعداد ستون, تعداد ردیف)

و رشته بعد از آن نوع عناصر آرایه را نشان می دهد. تصاویر معمولا با عدد غیر منفی ۸ بیتی تعریف می شوند(unsigned 8-bit integers)، بنابراین بارگذاری تصویر و تبدیل آن به آرایه نوع آرایه را به unit8 تنظیم می کند. در دومین قسمت ما تبدیل به طیف سیاه و سفید را داریم و یک آرایه با یک ورودی اضافه «f” ایجاد می کنیم. این یک فرمان کوتاه برای تنظیم نوع به ممیز شناور است. توجه کنید که تصویر طیف سیاه و سفید تنها دو مقدار در شکل تاپل دارند. واضح است که چون داده های رنگ ندارد.

عنصر های آرایه با اندیس ها(index) در دسترس هستند. مقدار در مختصات i و j با رنگ کانال k به شکل زیر در دسترس است:

value= im[i,j,k]

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

im[i,:] = im[j,:] # set the values of row i with values from row j

im[:,i] = 100 # set all values in column i to 100

im[:100,:50].sum() # the sum of the values of the first 100 rows and 50 columns

im[50:100,50:100] # rows 50-100, columns 50-100 (100th not included)

im[i].mean() # average of row i

im[:,-1] # last column

im[-2,:] (or im[-2]) # second to last row

 

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

 

عملیات های زیادی روی آرایه ها وجود دارند. هر زمان در طول کتاب به هر کدام نیاز پیدا کردیم در آنجا به معرفی و توضیح آن خواهیم پرداخت.

 

انتقال طیف خاکستری(Graylevel Transforms)

بعد از اینکه تصاویر را در آرایه های NumPy خواندیم، می توانیم هر عملیات ریاضی که دوست داریم روی آن انجام دهیم. یک مثال ساده انتقال طیف خاکستری در تصویر است. هر تابعی که بر روی یک محدوده صفر تا ۲۵۵ ( یا اگر شما بخواهید صفر تا یک) را به خودش نگاشت کند به معنی آن است که دامنه خروجی با دامنه ورودی یکی است). اینجا چند مثال آمده است:

 

from PIL import Image

from numpy import *

im = array(Image.open('empire.jpg').convert('L'))

im2 = 255 - im # معکوس کردن تصویر

im3 = (100.0/255) * im + 100 # قرار دادن در محدوده 100...200

im4 = 255.0 * (im/255.0)**2 # مربع کردن

در مثال اول ما دامنه خاکستری را معکوس کرده ایم، در مثال دوم مقادیر هر عنصر را در محدوده ۱۰۰ تا ۲۰۰ آورده ایم و در مثال سوم ما تابع درجه دوم را فراخوانی کرده ایم، که مقادیر بخش های تیره را کمتر می کند. تصویر ۱-۴ توابع را نشان می دهد و تصویر ۱-۵ تصویر های خروجی را نشان می دهد. شما می توانیم مقادیر بزرگتر و کوچکتر هر تصویر را با استفاده از دستور زیر به دست آورید

print int(im.min()), int(im.max())

تصویر ۱-۴. مثالهایی از انتقال طیف خاکستری. سه تابع مثال در تصویر به صورت خط ممتد و طیف اصلی به صورت خط چین دیده می شوند.

تصویر ۱-۵. انتقال طیف خاکستری. اعمال توابع تصویر ۱-۴: معکوس کردن مقدار هر عنصر با دستور f (x) = 255 − x سمت چپ، محدود کردن تصویر با استفاده از f (x) = (100/255)x + 100 وسط، انتقال مربعی با استفاده از f (x) = 255(x/255)2 سمت راست.

اگر شما برای هر مثال بالا دستور گرفتن کمترین و بیشترین مقدار را فراخوانی کنید نتیجه چنین خواهد شد:

 


 

2 255

0 253

100 200

0 255

انتقال معکوس آرایه می تواند با تابع fromarray بسته PIL به شکل زیر انجام شود:

pil_im = Image.fromarray(im)

اگر شما عملیات هایی برای تغییر نوع از uint8 به نوع داده دیگر انجام دهید، مانند im3 یا im4 در مثال بالا، برای ساخت یک تصویر PIL نیاز به تبدیل مجدد آن به صورت قبل دارید:

pil_im = Image.fromarray(uint8(im))

اگر از نوع داده ورودی مطمئن نیستید، به دلیل اینکه این کار اطمینان بخش است بهتر است آن را انجام دهید.توجه داشته باشید که NumPy همیشه نوع داده آرایه را به پایین ترین نوعی که داده عناصر که هم اکنون دارد را می تواند نگه دارد، تغییر می دهد.ضرب یا تقسیم با اعداد ممیز شناور نوع داده را از عدد صحیح(integer) به شناور(float) تغییر می دهد.


 

تغییر اندازه تصویر(Image Resizing)

آرایه های NumPy ابزار اصلی ما برای کار با تصاویر و داده ها هستند. راه آسانی برای تغییر اندازه آرایه ها وجود ندارد، و ما اکنون می خواهیم این کار را روی تصاویرمان انجام دهیم. شما می توانید از مبدل شی تصویر PIL که قبلتر نشان داده شد برای ایجاد یک تابع تغییر اندازه ساده استفاده کنید. به فایل imtools.py خط های زیر را اضافه کنید:

def imresize(im,sz):

    """ Resize an image array using PIL. """

    pil_im = Image.fromarray(uint8(im))

    return array(pil_im.resize(sz))

این تابع در آینده خیلی به درد بخور است.


 

متعادل سازی نمودار ستونی(Histogram Equalization)

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

تابع انتقال، در این مثال، تابع توزیع تراکم (cdf) مقادیر پیکسل های تصویر است. (نرمال شده برای اینکه نگاشتی از محدوده مقادیر پیکسل ها به مقادیر مورد نظر داشته باشد).

اینجا چگونگی انجام آن نشان داده شده است، این تابع را به فایل imtools.py اضافه کنید:

def histeq(im,nbr_bins=256):

    """ Histogram equalization of a grayscale image. """

    # get image histogram

    imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)

    cdf = imhist.cumsum() # تابع توزیع تراکم

    cdf = 255 * cdf / cdf[-1] # نرمال کردن

    با استفاده از درون یابی خطی از cdf برای پیدا کردن مقادیر جدید پیکسل ها استفاده می کنیم#

    im2 = interp(im.flatten(),bins[:-1],cdf)

    return im2.reshape(im.shape), cdf

تابع یک تصویر طیف سیاه و سفید و یک عدد که نشان دهنده تعداد مقادیری است که برای استفاده در نمودار ستونی باید داشته باشد، را به عنوان ورودی می گیرد. سپس یک تصویر با نمودار ستونی معادل می دهد. این کار با استفاده از تابع توزیع تراکم انجام شده است. با استفاده از آن مقادیر پیکسل ها نگاشت شده اند. به استفاده جزء آخر دقت کنید که (index-1) از cdf برای نرمال کردن بین صفر تا ۱ استفاده شده است.

آن را روی یک تصویر شبیه به زیر امتحان کنید:

from PIL import Image

from numpy import *

im = array(Image.open('AquaTermi_lowcontrast.jpg').convert('L'))

im2,cdf = imtools.histeq(im)

تصاویر ۱-۶ و ۱-۷ نمایش مثالهای متعادل سازی نمودار ستونی است. ردیف بالا نمودار ستونی خاکستری را قبل و بعد از متعادل سازی با استفاده از نگاشت cdf نشان میدهد. همانطور که می توانید ببینید، کنتراست و تقابل تصویر بیشتر شده است. حالا جزییات بخش تاریکتر تصویر واضح تر دیده می شوند.

تصویر ۱-۶. مثال متعادل سازی نمودار ستونی. در سمت چپ تصویر اصلی و نمودار ستونی را می بینید. در وسط تابع توزیع طیف خاکستری قرار دارد. در سمت راست تصویر و نمودار ستونی بعد از متعادل سازی قرار دارد.

تصویر ۱-۷. مثال متعادل سازی نمودار ستونی. در سمت چپ تصویر اصلی و نمودار ستونی آن را می بینید. در وسط ترسیمی از تابع انتقال طیف خاکستری قرار دارد. در سمت راست تصویر و نمودار ستونی بعد از متعادل سازی قرار دارد.


 

میانگیری تصاویر(Averaging Images)

یک راه ساده برای کاهش اختلال(noise) در تصویر استفاده از میانگیری است. همچنین برای اعمال تاثیرات هنری نیز از آن استفاده می شود. محاسبه میانگین یک تصویر از میان لیستی از تصاویر کار سختی نیست.

تصور کنید که تمام تصاویر لیست اندازه یکسانی دارند، ما می توانیم با جمع مقادیر تمام پیکسل های تصاویر و تقسیم آن بر تعداد پیکسل ها این کار را انجام دهیم. این تابع را به فایل imtools.py اضافه کنید:

def compute_average(imlist):

    """ Compute the average of a list of images. """

    # open first image and make into array of type float

    averageim = array(Image.open(imlist[0]), 'f')

    for imname in imlist[1:]:

        try:

            averageim += array(Image.open(imname))

        except:

            print imname + '...skipped'

    averageim /= len(imlist)

    # return average as uint8

    return array(averageim, 'uint8')

یک اداره خطا هم برای رد شدن از روی تصاویری که امکان باز کردن آنها وجود نداشت گذاشتیم. یک راه دیگر برای محاسبه میانگین تصاویر استفاده از تابع mean است. پیش نیاز آن این است که تمام تصاویر در آرایه ها قرار گرفته باشند که اگر تعداد تصاویر زیاد باشد حافظه زیادی مصرف می کند. ما از این تابع در بخش بعد استفاده می کنیم.


 

بررسی جز‌‌‌‌‌ء اصلی(Principal Component Analysis)

یکی از تکنیک ها مفید برای کاهش ابعاد تصویر به اندازه بهینه بررسی جز‌ء اصلی (PCA) است. همچنین از این روش برای نمایش تغییرات داده های آموزشی با استفاده از حداقل تعداد ممکن ابعاد استفاده می شود. حتی یک تصویر ۱۰۰*۱۰۰ پیکسلی طیف سیاه و سفید ۱۰۰۰۰ موقعیت(هر پیکسل یک موقعیت است که در بردارنده یک مقدار است) دارد و یک فضا با ۱۰۰۰۰ موقعیت را می گیرد. یک تصویر یک مگا پیکسلی میلیون ها موقعیت دارد. با چنین ابعاد بزرگی، جای تعجب نیست که کاهش فضای ابعاد در برنامه های بینایی ماشین خیلی به درد خور است.

ماتریس تصویر مشتق از PCA می تواند به عنوان تغییرات مختصات در سیستم مختصات در نظر گرفته شود. در این حالت مختصات به ترتیب اولویت بالا به پایین قرار می گیرند. برای اعمال PCA روی داده های تصویر، نیاز است که تصاویر(مثلا با استفاده از تابع flatten بسته NumPy) به شکل یک بردار یک بعدی نمایش داده شوند.

تصاویر مسطح شده در یک ماتریس ذخیره می شوند، یک ردیف به ازای هر تصویر. ردیف ها سپس بر اساس تصویر میانگین متمرکز می شوند. بعد از اینکار به محاسبه حوزه ها و محدوده های حرکت می پردازیم. برای پیدا کردن اجزاء اصلی، از تجزیه مقادیر منفرد(singular value decomposition یا SVD) استفاده می شود. اما اگر ابعاد تصویر بزرگ باشد، یک فن و قلق خاص می تواند به جای آن استفاده شود. دلیل آن این است که SVD در این موارد بسیار کند عمل می کند. در اینجا کد مثالی را می توانید ببینید:

from PIL import Image

from numpy import *

def pca(X):

    """ Principal Component Analysis

    input: X, matrix with training data stored as flattened arrays in rows

    return: projection matrix (with important dimensions first), variance

    and mean."""

    # get dimensions

    num_data,dim = X.shape

    # center data

    mean_X = X.mean(axis=0)

    X = X - mean_X

    if dim>num_data:

        # PCA - compact trick used

        M = dot(X,X.T) # covariance matrix

        e,EV = linalg.eigh(M) # eigenvalues and eigenvectors

        tmp = dot(X.T,EV).T # this is the compact trick

        V = tmp[::-1] # reverse since last eigenvectors are the ones we want

        S = sqrt(e)[::-1] # reverse since eigenvalues are in increasing order

        for i in range(V.shape[1]):

            V[:,i] /= S

    else:

        # PCA - SVD used

        U,S,V = linalg.svd(X)

        V = V[:num_data] # only makes sense to return the first num_data

        # return the projection matrix, the variance and the mean

    return V,S,mean_X

این تابع در ابتدا داده ها متمرکز می شوند. این کار با کم کردن از میانگین در هر بعد صورت می گیرد. سپس در هر صورت استفاده از فن فشرده یا SVD بردار های ویژه متناظر با بزرگترین مقادیر ویژه از ماتریس کوواریانس محاسبه می شوند. در اینجا ما از تابع range استفاده کرده ایم. این تابع یک مقدار عدد صحیح می گیرد و یک لیست از اعداد صحیح (صفر تا n-1) بر می گرداند. می توانید از معادل آن یعنی arange نیز استفاده نمایید، که به شما یک آرایه برمیگرداند، یا اینکه از xrange استفاده نمایید، که یک generator بر می گرداند(و ممکن است در افزایش سرعت موثر باشد). ما در این کتاب از range استفاده می کنیم.

ما اگر تعداد نقاط داده از ابعاد بردارش کمتر شود به جای استفاده از SVD به فن محاسبه بردار ویژه از کوواریانس کوچکتر ماتریس Xxt روی آورده ایم. همچنین راه هایی است که می توان با استفاده از آن تنها به محاسبه بردارهای ویژه متناظر با k تعداد مقادیر ویژه(k تعداد ابعاد مورد نظر است) پرداخت که سریع تر است.

ما همین جا این موضوع را بدلیل اینکه واقعا خارج از محدوده این کتاب است تمام می کنیم. ردیف های ماتریس V قائم هستند و دربردارنده مسیر های هماهنگ هستند و این موضع منجر به کاهش واریانس داده های آموزشی می شود.

اجازه بدهید از این مثال برای تصاویر یک کاراکتر استفاده کنیم. فایل fontimages.zip دربردارنده تصاویر بند انگشتی از کاراکتر a که به اشکال مختلفی نوشته شده و اسکن شده است هستند. 2359 قلم از مجموعه قلم هایی که به رایگان در دسترس است(http://webstaff.itn.liu.se/~marso). در نظر بگیرید که نام های این تصاویر در یک لیست ذخیره شده اند، اسمش را imlist میگذاریم، طبق کد قبل که آن را در فایل pca.py ذخیره کرده ایم اجزاء اصلی به شکل زیر محاسبه و نمایش داده می شوند.

from PIL import Image

from numpy import *

from pylab import *

import pca

im = array(Image.open(imlist[0])) # open one image to get size

m,n = im.shape[0:2] # get the size of the images

imnbr = len(imlist) # get the number of images

# create matrix to store all flattened images

immatrix = array([array(Image.open(im)).flatten() for im in imlist],'f')

# perform PCA

V,S,immean = pca.pca(immatrix)

# show some images (mean and 7 first modes)

figure()

gray()

subplot(2,4,1)

imshow(immean.reshape(m,n))

for i in range(7):

    subplot(2,4,i+2)

    imshow(V[i].reshape(m,n))

show()

تصویر ۱-۸. تصویر میانه (چپ و بالا) و هفت حالت اول، با بیشترین تنوع در جهات.

توجه داشته باشید که تصاویر برای نمایش می بایست از یک بعدی به حالت اول برگردند. این کار با استفاده از تابع reshape صورت می گیرد. با اجرای مثال میبایست یک پنجره داشته باشید که مانند تصویر ۱-۷ کنار هم قرار گرفته اند. در اینجا ما از تابع subplot بسته PyLab برای قرار دادن چند ترسیم در یک پنجره استفاده کرد ایم.


 

استفاده از بسته Pickle

اگر می خواهید که بعضی نتایج یا داده ها را برای استفاده بعدی ذخیره کنید از بسته pickle استفاده کنید، این بسته همراه پایتون ارائه شده است و بسیار سودمند است. Pickle می تواند هر نوع شی پایتونی را به شکل رشته تبدیل کند. این فرایند با نام pickling شناخته می شود. بازسازی معکوس شی از شکل رشته unpicking نامیده می شود. این شکل رشته ای می تواند به راحتی ذخیره یا منتقل شود.

اجازه بدهید که با یک مثال این موضوع را شرح دهیم. در نظر بگیرید که می خواهیم یک تصویر میانگین و اجزاء اصلی تصاویر نوشته ها در بخش قبل را ذخیره کنیم. می تواند به شکل زیر باشد:

# save mean and principal components

f = open('font_pca_modes.pkl', 'wb')

pickle.dump(immean,f)

pickle.dump(V,f)

f.close()

همانطور که می توانید ببینید، چند شی می تواند در یک فایل ذخیره شود. چندین پروتکل برای فایل های .pkl در دسترس است و اگر در مورد انتخاب نامطمئن هستید. بهترین کار این است که به شکل دو دویی(binary) فایل ها را بخوانید و بنویسید. برای بارگذاری داده ها در یک نشست پایتون دیگر تنها از تابع load شبیه زیر استفاده کنید:

# load mean and principal components

f = open('font_pca_modes.pkl', 'rb')

immean = pickle.load(f)

V = pickle.load(f)

f.close()

توجه کنید که ترتیب شی ها باید یکسان باشد! همچنین نسخه بهینه شده ای از این بسته که با زبان C نوشته شده است با نام cpickle وجود دارد که تماما با بسته استاندارد pickle مطابق است. جزییات بیشتر درباره بسته pickle و مستندات آن در آدرس زیر در دسترس است:

http://docs.python.org/library/pickle.html

به عنوان یک یادگاری از این کتاب، ما از عبارت with برای مدیریت خواندن و نوشتن استفاده می کنیم. With یک سازنده است که در پایتون ۲.۵ معرفی شده است. با استفاده از آن می توانیم مدیریت باز کردن و بستن فایل ها( حتی اگر خطایی در هنگام باز کردن فایل ها روی دهد) انجام دهیم. کد باز کردن و ذخیره کردن فایل های با with شبیه زیر می شود:


 

# open file and save

with open('font_pca_modes.pkl', 'wb') as f:

    pickle.dump(immean,f)

    pickle.dump(V,f)

و :

# open file and load

with open('font_pca_modes.pkl', 'rb') as f:

    immean = pickle.load(f)

    V = pickle.load(f)

ممکن است که در ابتدا کمی عجیب به نظر برسد، اما یک سازنده خیلی مفید است. اگر شما این را دوست ندارید، می توانید از تابع های open و close مانند قبل استفاده کنید.

به عنوان یک معادل برای pickle، در بسته NumPy هم توابع ساده ای برای خواندن و نوشتن فایل های متنی وجود دارند که اگر ساختار داده پیچیده ای ندارید می توانند مفید باشند. برای نمونه برای ذخیره یک لیست نقاط که روی تصویر کلیک شده است و در یک آرایه با نام x قرار دارد روی یک فایل می توان از کد زیر استفاده کرد

savetxt('test.txt',x,'%i')

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

x = loadtxt('test.txt')

شما می توانید مستندات بیشتر را در آدرس زیر بیابید:

http://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html

در انتها، NumPy تابع های مجزایی برای ذخیره و خواندن آرایه ها دارد. برای جزییات بیشتر بخش save و load را در مستندات آنلاین ببنید.

 

دیدگاه جدیدی بگذارید