שימו לב: על מנת להריץ את התאים ב-Live Code, יש לייבא תחילה את ספרית numpy ע”י הרצת השורת הבאה:

import numpy as np
import matplotlib.pyplot as plt
import imageio

פעולות בסיסיות על מערכי numpy ותמונות#

ביחידה זו נכיר תכונות ופעולות שונות שניתן לבצע במערכי numpy, ונדגים אותם על המערכים “רגילים” כמו גם על מערכים המייצגים תמונות.

מימדי המטריצה#

גישה למימדי מטריצה קיימת#

נתחיל מהגדרת המטריצה הבאה:

b = np.array([[3, 4, 5], [6, 7, 8]])
print('b is \n' , b)
b is 
 [[3 4 5]
 [6 7 8]]

למדנו ביחידות הקודמות שlen תחזיר לנו את אורך האובייקט. מה לדעתם הערך שיוחזר מlen עבור המטריצה שלנו?

print(len(b))
2

כלומר, עבור מערכי numpy, len מחזירה את מספר השורות במטריצה.
במערך חד-מימדי len תחזיר את מספר האלמנטים במערך, בדומה לרשימה.

ואם נרצה לקבל את כל מימדי המטריצה שלנו? לצורך כך נשתמש בתכונה shape:

print(b.shape)
(2, 3)

שינוי מימדי מטריצות קיימות#

ניתן גם לשחלף את המטריצה שלנו ע”י שימוש בתכונה T (כמו Transpose).
שימו לך שמימדי המטריצה המשוחלפת התהפכו!

print('b is \n',b)
print('b transposed is \n',b.T)
print('b transposed dimensions are: ',b.T.shape)
b is 
 [[3 4 5]
 [6 7 8]]
b transposed is 
 [[3 6]
 [4 7]
 [5 8]]
b transposed dimensions are:  (3, 2)

מלבד שחלוף, קיימת פונקציה בשם reshape, המאפשרת לשנות את מבנה המטריצה. מתודה זה תשים את האיברים של המטריצה המקורית, לפי סדר השורות, במטריצה חדשה בעלת מימדים שהועברו בקלט.

c = b.reshape((3,2))
print(c)
[[3 4]
 [5 6]
 [7 8]]

**שימו לב **

המימדים המועברים בקלט המתודה reshape חייבים לייצג מטריצה אשר תכיל את כל איברי המערך ממנו הופעלה המתודה. אחרת נקבל שגיאה.

לדוגמאות לעיל, פלט המתודה reshape אינו זהה למטריצה המשוחלפת, זאת מכיוון שבמטריצה המשוחלפת השורות הופכות לעמודות, וב-reshape סדר האיברים לפני שורות נשמר

כעת נדגים מה כיצד הפעולות שראינו למעלה מתבטאות במטריצות המייצגות תמונות:

על מנת לקבל את מספר השורות והעמודות בתמונה נוכל להשתמש בתכונה shape:

im_dog = imageio.v3.imread('files/dog.png')
print(im_dog) 
[[ 88  88  88 ...  61  60  60]
 [ 89  88  88 ...  61  60  60]
 [ 89  89  89 ...  61  61  61]
 ...
 [143 143 145 ... 103 102 102]
 [144 145 149 ... 103 102 102]
 [144 145 149 ... 103 102 102]]
print(im_dog.shape)
(302, 335)

ועל מנת להפוך את התמונה על צידה נוכל להשתמש בתכונה T

im_transposed = im_dog.T
plt.imshow(im_transposed,cmap='gray')
<matplotlib.image.AxesImage at 0x7f036f69c110>
_images/0c03a26d45ff7762ef8f9273698fd8660c3774c43b0d7f3637d163258e6ad94e.png

גישה לערכים במטריצה#

חילוץ ערך בודד ממטריצה#

בדומה לרשימות, גם בnumpy ניתן לגשת לערכים ספציפיים בתמונה, כולל Slicing.

הייחודיות של Numpy היא שניתן לגשת לאיבר (או איברים) במטריצה (לדוגמא, mat) ע”י אינדקס השורות (x) והעמודות (y) יחדיו בתוך הסוגריים המרובעים בצורה הבאה: mat[x,y]

נדגים זאת בקוד הבא:

תחילה ניצור מטריצה 3X3 של סדרת המספרים בין 0 ל8:

a=np.arange(9).reshape((3,3))
print(a)
[[0 1 2]
 [3 4 5]
 [6 7 8]]

כעת נחלץ את האיבר האמצעי של המטריצה:

print(a[1, 1])
4

כלומר, חילצנו את האיבר שנמצא באינקס 1 של השורות (שורה שניה) ובאינדקס 1 של עמודות (עמודה שניה):

חילוץ תתי מטריצות#

כדי להחזיר שורה שלמה, פשוט לא נעביר אינדקס עמודות. לדוגמא, כך נחזיר את כל השורה השניה:

print(a[1])
[3 4 5]

ומה עם נרצה להחזיר את כל העמודה השניה? חייבים לשים משהו לפני הפסיק…
במקרה זה, נשים פשוט נקודותיים : בדומה לslciing נקודותיים מחזירה את כל הטווח (כלומר, את כל השורות).

לדוגמא, כך נחזיר את העמודה השניה:

print(a[:,1])
[1 4 7]

באופן אנלוגי לרשימות, גם כאן ניתן לבצע השמות של איבורים למטריצה קיימת באמצעות פירוט אינדקסים הרלוונטיים:

a[1, 1] = 10
print(a)
[[ 0  1  2]
 [ 3 10  5]
 [ 6  7  8]]

ניתן גם להחזיר או לבצע הרשמה על תת-קבוצה של שורות/עמודות. לדוגמא:

a[0,0:2] = a[2,1:3]*10
print(a)
[[70 80  2]
 [ 3 10  5]
 [ 6  7  8]]

ניתן אף לפרט בדיוק אילו אינדקסים רוצים להחזיר ובאיזה סדר באמצעות רשימה (או Iterable מספרי אחר) בשורות את בעמודות. לדוגמא:

print(a[[2,1,2], 2])
[8 5 8]

תרגול: החזרת תתי-מטריצות באמצעות Slicing#

תחילה נגדיר את המטריצה mat הבאה:

mat= np.array([[0, 1, 2,3,4,5],
            [10, 11, 12,13,14,15],
            [20, 21, 22,23,24,25],
            [30, 31, 32,33,34,35],
            [40, 41, 42,43,44,45],
            [50, 51, 52,53,54,55]])
print(mat)
[[ 0  1  2  3  4  5]
 [10 11 12 13 14 15]
 [20 21 22 23 24 25]
 [30 31 32 33 34 35]
 [40 41 42 43 44 45]
 [50 51 52 53 54 55]]

כעת, עבור כל אחת מארבעת הצבעים במטריצה להלן, כתוב שורה בודדת המבצעת Slicing על mat כדי לקבל את תת-מטריצה המסומנת ע”י אותו הצבע:

חיתוך שורות #

# Write you code here

חיתוך עמודות #

# Write you code here

תת מטריצה רציפה #

# Write you code here

תת-מטריצה עם קפיצות #

# Write you code here

עריכת מטריצה קיימת#

באופן אנלוגי לרשימות, גם כאן ניתן לערוך מטריצה קיימת באמצעות השמה.

ניצור תחילה מטריצה חדשה כפי שעשינו קודם:

a=np.arange(9).reshape((3,3))
print(a)
[[0 1 2]
 [3 4 5]
 [6 7 8]]

כעת נבצע השמה של איברים למטריצה קיימת באמצעות פירוט האינדקסים הרלוונטיים:

a[1, 1] = 10
print(a)
[[ 0  1  2]
 [ 3 10  5]
 [ 6  7  8]]

ניתן גם לבצע השמה על חלק מהשורות או העמודות. לדוגמא:

a[0,0:2] = a[2,1:3]*10
print(a)
[[70 80  2]
 [ 3 10  5]
 [ 6  7  8]]

מאחר שתמונות הן מטריצות מספרים בטווח 0-256, ניתן לערוך תמונות באמצעות שינוי ערכי המטריצה.

למטה נראה שתי דוגמאות לעריכת תמונה באמצעות Slicing.

נתחיל מקטע הקוד הבא:

new_fig = np.zeros((100,100))
new_fig[::2,::2] = 255

ודאו שהבנתם כיצד שתי השורות ההלו יוצרות את התמונה המופיעה למטה:

plt.imshow(new_fig, cmap=plt.cm.gray)
<matplotlib.image.AxesImage at 0x7f036f43ac50>
_images/463cd4041908230c9fef10ebd1a1893361d962827829eac91a7dcafbe2312e05.png

כעת, נניח שאנחנו רוצים להציג תמונה ברזולוציה נמוכה יותר, כלומר, לייצג תמונה עם פחות פיקסלים בזכרון (חשבו: מדוע שנרצה לעשות זאת?). בדוגמא הבאה, עבור ריבועים (לא חופפים) של 10X10 פיקסלים, נשאיר את הפיקסל השמאלי העליון:

im2=im_dog[::10,::10]
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(im_dog, cmap=plt.cm.gray)
plt.subplot(1, 2, 2)
plt.imshow(im2, cmap=plt.cm.gray)
<matplotlib.image.AxesImage at 0x7f036c33cd50>
_images/a39c1ebdd5dd57e8e92b7bd280813f2a03980243c3d9aecf714656f5acb977d7.png

ערכים בוליאניים והשוואות במערכי numpy#

השוואות במערכי numpy מתבצעות בדומה לאופרטורים מתמטיים.
אם משווים בין שני מערכים מאותם מימדים, הההשוואה נעשית איבר איבר.
אם משווים בין מערך לערך מסוים, כלל האיברים במערך יושוו לאותו הערך.
בשני המקרים, נקבל מערך בוליאני במימדי המערך (או המערכים) שבפעולת ההשוואה .

דוגמאות:

a = np.arange(6).reshape(2,3)
print("a: ", a)

b = np.arange(4,-2,-1).reshape(2,3)
print("b: ", b)

print(a<2)
print(a==b)
a:  [[0 1 2]
 [3 4 5]]
b:  [[ 4  3  2]
 [ 1  0 -1]]
[[ True  True False]
 [False False False]]
[[False False  True]
 [False False False]]

בהינתן מערך המכיל ערכים בוליאניים, ניתן להשתמש בany על מנת לבדוק האם לפחות אחד מהערכים במערך הוא True, כמו פעולת or בין כל איברי המערך.
באופן אנלוגי, ניתן גם להשתמש בall על מנת לבדוק האם כל הערכים במערך הוא True, כמו פעולת and בין כל איברי המערך.

comp1=a==b
print("### a==b ###")
print("any: ", comp1.any())
print("all: ",comp1.all())

print("### a<6 ###")
comp2=a<6
print("any: ",comp2.any())
print("all: ",comp2.all())

print("### a>6 ###")
comp3=a>6
print("any: ",comp3.any())
print("all: ",comp3.all())
### a==b ###
any:  True
all:  False
### a<6 ###
any:  True
all:  True
### a>6 ###
any:  False
all:  False

כפי שלמדנו ניתן להתייחס לערכים הבוליאניים False ו-True גם כמספרים 0 ו-1 בהתאמה.

לכן, ניתן להפעיל עליהם גם מתודות numpy.

בדוגמא למטה, אנו מפעילים את nonzero, המחזירה את כל האינדקסים של המערך בהם מופיע ערך שאינו 0, על מטריצה בוליאנית (כלומר, כל מקום בו מופיע True).

print("### a==b ###")
print(comp1.nonzero())
### a==b ###
(array([0]), array([2]))

דוגמא נוספת היא המתודה sum הסוכמת את כל איברי המטריצה

print("### a==b ###")
print(comp1.sum())
### a==b ###
1

חשבו

מדוע החזירו המתודות nonzero ו-sum את הערכים שהודפסו לעיל עבור a==b?

תרגול מסכם#

בשאלות הבאות תתנסו במימוש הפונקציותany וall בעצמכם:

התחילו ממימוש הפונקציה array_any אשר מקבלת מטריצה בוליאנית, ומחזירה אם קיים בו איבר שהוא True. אין להשתמש במתודה any!

יש לממש את הפתרון בשורה אחת בלבד.

def array_any(array):
    # Write your code here
    pass

כעת ממשו את הפונקציה array_all אשר מקבלת מערך בוליאני דו-מימדי, ומחזירה אם כל האיברים בו הם True. אין להשתמש במתודה all!

יש לממש את הפתרון בשורה אחת בלבד.

def array_all(array):
    # Write your code here
    pass