Επεξεργασία Εικόνας Το Πρόβλημα Μια ασπρόμαυρη φωτογραφία μπορεί να προσεγγισθεί από έναν πίνακα με κουκίδες, όπου κάθε κουκίδα απεικονίζεται με την κατάλληλη ποσότητα του «γκρίζου». Οι κουκίδες ονομάζονται pixels. Η ασπρόμαυρη φωτογραφία είναι ένας πίνακας διαστάσεων (π.χ. 3390x4200=14238000). Το (i,j) στοιχείο του πίνακα, ποσοτικοποιεί το επίπεδο του γκρίζου του pixel. Το επίπεδο του γκρίζου, στις ασπρόμαυρες φωτογραφίες αναπαριστάται με 256 διαφορετικά επίπεδα [0-255], όπου το 0 αντιστοιχεί στο μαύρο και το 255 στο λευκό. Το 255 επιτρέπει την κωδικοποίηση μόνο με ένα byte.
Το Πρόβλημα Ένα τμήμα Α (9x8) pixels σε κάποιο σημείο της ασπρόμαυρης φωτογραφίας θα είναι: A(2000:2008,2856:2863)= 213 210 201 202 160 114 63 58 171 176 175 166 71 48 47 31 174 181 165 147 59 42 41 30 179 184 151 124 52 45 44 39 169 163 120 95 48 51 48 47 139 115 76 63 40 49 43 46 106 73 46 47 39 47 38 47 74 53 38 47 44 44 35 53 49 47 38 50 44 39 30 55
Το Πρόβλημα Για την αποθήκευση των τιμών των pixels χρησιμοποιείται ο τύπος δεδομένων uint8, που είναι μια μορφή μη προσημασμένου ακεραίου του ενός byte, που είναι πιο οικονομική από αυτήν της μορφής double των 8 bytes που υποστηρίζει αριθμητική κινητής υποδιαστολής.
Το Πρόβλημα Σε μια έγχρωμη φωτογραφία, το χρώμα μπορεί να παρασταθεί με τρεις πίνακες, έναν για κάθε ένα από τα τρία χρώματα rgb. Όπως και για την ασπρόμαυρη, έτσι και για την έγχρωμη, χρησιμοποιείται η ίδια κλίμακα [0-255], για την αναπαράσταση της έντασης του χρώματος. Αν τα A red, A green, A blue, είναι οι κωδικοποιήσεις σε μορφή πίνακα, για τα τρία χρώματα της εικόνας, τότε το pixel i, j απεικονίζεται με την rgb χρωματική τριάδα: A red i, j, A green i, j, A blue i, j.
Το Πρόβλημα Οι πιο ευρέως διαδεδομένες εικόνες είναι αυτές με κωδικοποίηση jpeg. Με την εντολή imread μπορούμε να διαβάσουμε τα δεδομένα των pixels μιας εικόνας. Αφού τα διαβάσουμε, τα δεδομένα αυτά θα βρεθούν στο περιβάλλον εργασίας του Matlab και μπορούμε να τα δούμε με την εντολή imshow: fname = [pwd '\InsightData\12_4\Cornell_Clock.jpg'] A = imread(fname,'jpg'); imshow(a); Το πλήρες μονοπάτι του αρχείου Cornell_Clock.jpg εκχωρείται στο fname. Ο κώδικας αυτός εμφανίζει την έγχρωμη φωτογραφία που συναντήσαμε ήδη. * Μερικές φορές, όταν ένα script δεν δουλεύει, καλά είναι να το βάλουμε στον Editor για να βρούμε πιο εύκολα τυχόν λάθη που υπάρχουν (με κόκκινο).
Το Πρόβλημα Καθώς όπως ήδη αναφέρθηκε, η αναπαράσταση μιας έγχρωμης φωτογραφίας απαιτεί έναν τρισδιάστατο πίνακα. Η αναφορά σε κάποιο στοιχείο του, γίνεται με τρεις δείκτες: π.χ. το Α(20, 400, 3) είναι η τιμή του μπλε στο pixel (20, 400). Η εσωτερική συνάρτηση imread εφαρμόζεται σε μια έγχρωμη εικόνα με ύψος m pixels και πλάτος n pixels και επιστρέφει έναν πίνακα A (mxnx3), με την ιδιότητα ότι τα A i, j, 1, A i, j, 2, A i, j, 3, είναι οι τιμές έντασης του κόκκινου, πράσινου, μπλε αντίστοιχα, για το pixel i, j. Στον κώδικα που ακολουθεί φαίνεται ένας τρόπος για να καταργηθούν η πράσινη και η μπλε συνιστώσες και να διατηρηθεί η κόκκινη.
Ο κώδικας: Το Πρόβλημα Ared = A; % Αντέγραψε Ared(:,:,2) = 0; % Μηδένισε τα πράσινα pixels Ared(:,:,3) = 0; % Μηδένισε τα μπλε pixels imshow(ared) % Εμφάνισε αυτό που απομένει Ο τελεστής : προσφέρει ευκολία για τον παραπάνω προσδιορισμό με την χρήση των βρόχων for: [m,n,p] = size(a); Ared = uint8(zeros(m,n,p)); for i=1:m for j=1:n Ared(i,j,1) = A(i,j,1); Ared(i,j,2) = 0; Ared(i,j,3) = 0; end end imshow(ared)
Το Πρόβλημα Με την εσωτερική συνάρτηση rgb2gray μπορούμε να μετατρέψουμε μια έγχρωμη εικόνα (έναν τρισδιάστατο πίνακα δηλαδή) σε μια ασπρόμαυρη (δηλαδή σε έναν δισδιάστατο). B = rgb2gray(a); imshow(b) fname=[pwd '\MyData\Cornell_Clock_Gray.jpg']; imwrite(b,fname,'jpg') Το script μετατρέπει την εικόνα και αποθηκεύει την ασπρόμαυρη στον φάκελο MyData.
Θέλουμε να αναπτύξουμε ένα πρόγραμμα για να ανιχνεύουμε τα στίγματα, σε μια ασπρόμαυρη εικόνα, τα οποία στην συνέχεια θα τα απομακρύνουμε, αλλά και για την ανίχνευση των ακμών για περεταίρω χρήση.
Το Πρόβλημα Ο πίνακας Β είναι ένας πίνακας uint8 των τιμών του γκρίζου, από 0 έως 255. Η συνάρτηση imwrite χρησιμοποιείται για να δημιουργούμε αρχεία εικόνων. Έχει τρεις παραμέτρους εισόδου: a) Τον πίνακα. b) Το πλήρες όνομα του αρχείου στο οποίο θα αποθηκευτούν οι τιμές των pixels. c) Η μορφή του αρχείου.
Το Πρόβλημα Μπορούμε να ξεχωρίσουμε ένα τμήμα μιας εικόνας και να το απεικονίσουμε η να το επεξεργαστούμε: C = B(850:1150,2350:2650); for k=1:200 i = floor(1+rand(1)*300); j = floor(1+rand(1)*300); C(i:i+1,j:j+1) = floor(10*rand(2,2)); end imshow(c) Τα i,j είναι οι συντεταγμένες των 200 τυχαίων σημείων Αντικαθιστά τα 200 τυχαία σημεία με στίγματα 2x2 Το ανωτέρω script ανασύρει ένα τμήμα 301x301 της ασπρόμαυρης εικόνας του πύργου με το ρολόι, προσθέτει με τυχαίο τρόπο 200 «στίγματα» (2x2) και εμφανίζει το αποτέλεσμα. Σκοπός μας είναι να κάνουμε ένα πρόγραμμα που θα αφαιρεί (θα φιλτράρει) αυτά τα στίγματα, τα οποία -ως μαύρα που είναιέχουν τιμές πολύ μικρότερες απ αυτές των γειτονικών pixels.
Εάν πάρουμε ένα «μολυσμένο» τμήμα του ουρανού (6x6), αυτό θα είναι της μορφής: X = 155 160 155 161 154 160 150 157 160 159 161 155 157 154 155 152 6 7 5 3 157 157 156 158 160 160 161 158 161 155 163 156 159 159 156 159 Βλέπουμε πως μια λογική διόρθωση, θα ήταν να αντικαταστήσουμε τις «μολυσμένες» τιμές των pixels με τιμές που είναι πιο «συνηθισμένες» στην «γειτονιά» τους. Για τον σκοπό αυτό, πρέπει πρώτα να ποσοτικοποιήσουμε τις έννοιες: «γειτονιά» και «συνηθισμένες τιμές».
Λέμε πως τα pixels i 1, j 1 και i 2, j 2 είναι γειτονικά, εάν: i 1 i 2 1 και j 1 j 2 1. Έτσι προκύπτει πως ένα εσωτερικό pixel έχει 9 γείτονες (μαζί με τον εαυτό του) ενώ τα pixel των ακμών έχουν 4 ή 6 γείτονες (μαζί με τον εαυτό τους). X = 155 160 155 161 154 160 150 157 160 159 161 155 157 154 155 152 6 7 5 3 157 157 156 158 160 160 161 158 161 155 163 156 159 159 156 159
Η ιδέα για την αποκατάσταση είναι να πάμε σε κάθε ένα pixel και να το αντικαταστήσουμε με την τιμή του διαμέσου (median) των γειτόνων του. Αυτή η στρατηγική, εφαρμοζόμενη στο «μολυσμένο τμήμα» 6x6 του πίνακα Χ(2:5, 2:5) αλλάζει ως εξής: 161 155 160 6 157 5 159 157 152 161 7 161 3 163 157 159 155 155 155 152 157 157 157 157 155 158 152 156 157 157 157 158 Αυτό το φίλτρο λέγεται φίλτρο διαμέσου (median). Όπως βλέπουμε από το αποτέλεσμα, η τεχνική αυτή είναι επιτυχής, καθώς τα στίγματα στον ουρανό της φωτογραφίας είναι ορισμένα και οι τιμές στα pixels του ουρανού είναι ομοιόμορφες.
Αυτό το φίλτρο αφήνει σχεδόν ανεπηρέαστα τα pixels που μοιάζουν με τα γειτονικά τους και προσαρμόζει αυτά που διαφέρουν. Πρέπει να πούμε πως είναι πιο αποτελεσματικό, από το αν εφαρμόζαμε ένα φίλτρο μέσης τιμής (αντί διάμεσο). Αυτό φαίνεται στον ίδιο πίνακα, αν αντικαταστήσουμε τις τιμές των pixels με την μέση τιμή των γειτονικών τους pixels. Βλέπουμε πως οι τιμές των στιγμάτων «τραβάνε» προς τα κάτω τις τιμές των γειτονικών τους pixels, καθώς επίσης και ότι οι αναθεωρημένες τιμές δεν έρχονται τελικά στην περιοχή των «συνηθισμένων» τιμών. 161 155 160 6 157 5 159 157 152 161 7 161 3 163 157 159 140 124 123 90 123 90 140 123 124 141 90 124 91 124 124 141
Η υλοποίηση της συνάρτησης MedianFilter απαιτεί έναν διπλό βρόχο για να πραγματοποιήσει το ολοκληρωμένο πέρασμα από όλα τα pixels του πίνακα και για να αποφευχθούν αναφορές εκτός των ορίων, κοντά στα άκρα της εικόνας. Η εσωτερική συνάρτηση median όταν εφαρμόζεται σε ένα διάνυσμα επιστρέφει την διάμεσο. Η διάμεσος είναι η αριθμητική τιμή που χωρίζει το άνω από το κάτω μισό μιας σειράς αριθμών και βρίσκεται αν πάρουμε τον μεσαίο αριθμό, αφού τους βάλουμε σε σειρά μεγέθους. Π.χ., στην σειρά των αριθμών: {3, 3, 5, 9, 11} ο διάμεσος είναι ο 5. Για να γίνει αυτό, ο πίνακας Neighbors 3x3, ανασχηματίζεται σε ένα διάνυσμα 9x1 μέσω του Neighbors(:).
function B = MedianFilter(A) % A is an m-by-n uint8 array. % B is an m-by-n uint8 array obtained from A % by median filtering [m,n] = size(a); B = zeros(m,n,'uint8'); for i=1:m for j=1:n % The 3-by-3 matrix of neighbors... imin = max(1,i-1); imax = min(m,i+1); jmin = max(1,j-1); jmax = min(n,j+1); Neighbors = A(iMin:iMax,jMin:jMax); % The median value... B(i,j) = median(neighbors(:)); end end
Η ανίχνευση ακμών είναι πρόβλημα αντίστοιχο με την ανίχνευση των στιγμάτων, καθώς έχουμε κι εδώ απότομες αλλαγές στις τιμές των pixels στο επίπεδο του γκρίζου. Εδώ όμως δεν θέλουμε να τις εξαλείψουμε αλλά να τις χρησιμοποιήσουμε για τις περεταίρω ανάγκες του προγράμματος. Εάν ξαναπάρουμε ένα τμήμα 3x3 pixels, ο ρυθμός μεταβολής ρ ij στο pixel i, j ορίζεται ως η μέγιστη διαφορά μεταξύ του A i, j και των γειτόνων του: A i 1, j 1, A i 1, j, A i 1, j + 1, A i, j 1, A i, j + 1, A i + 1, j 1, A i + 1, j, A i + 1, j + 1. Αυτό προϋποθέτει πως το pixel i, j δεν είναι στο άκρο της εικόνας, γιατί τότε θα προέκυπτε σφάλμα δείκτηεκτός-ορίων.
Αν πάρουμε ως παράδειγμα υπολογισμού την: Α 8: 10, 20: 22 = Τότε ο ρ 9,21 = 242 18 = 224. 240 26 18 237 242 31 236 241 241 Σκοπός μας είναι να έχουμε μια οριακή τιμή κατωφλίου τ (threshold) για το ρ ij και να ορίσουμε ένα pixel i, j ως pixel ενδιαφέροντος αν ρ ij τ. Βέβαια μ αυτόν τον τρόπο, ένα pixel στίγματος είναι πολύ πιθανό να έχει υψηλό ρυθμό μεταβολής και έτσι να αναγνωριζόταν (εσφαλμένα) ως ένα pixel ενδιαφέροντος.
Όμως οι ακμές έχουν τέτοια δομή ώστε, αν έχουμε επισημάνει όλα τα pixel ενδιαφέροντος σε μια εικόνα, τότε περιμένουμε να δούμε καμπύλες ή γραμμές στην περιοχή των πραγματικών ακμών. Στο παράδειγμα της εικόνας του πύργου με το ρολόι, ένα τμήμα της: 168 153 134 145 121 60 47 51 58 X = 178 152 124 134 109 53 49 56 53 176 157 126 122 110 64 55 52 52 158 138 108 120 136 68 53 51 56 Περικλείει την έντονη κάθετη ακμή που φαίνεται ευδιάκριτα στον πύργο με το ρολόι. Αν και είναι ίσως η πιο αιχμηρή ακμή σε όλη την εικόνα, παρατηρούμε πως η μετάβαση (από το φωτεινό στο σκοτεινό) δεν γίνεται στιγμιαία. Η ανίχνευση των ακμών είναι μια πολύπλοκη διαδικασία.
Προχωρούμε στην κατασκευή μιας εικόνας με την ιδιότητα ότι το pixel i, j είναι μαύρο (0) αν ρ ij < τ και άσπρο (255) αν ρ ij τ. Η συνάρτηση Edges δημιουργεί μια αναπαράσταση jpeg αυτής της εικόνας ακμής. Βέβαια, όπως είναι αναμενόμενο, στον υπολογισμό του ρυθμού μεταβολής δεν συμμετέχουν μόνον οι ακμές, καθώς σε μια εικόνα μπορεί να υπάρχουν και άλλα στοιχεία που μπορεί να έχουν έντονο ρυθμό μεταβολής, χωρίς να είναι ακμές. Στα δύο πλαίσια 3x3 είδαμε -σε αρχικό στάδιο- την αντιμετώπιση των προβλημάτων του φιλτραρίσματος στιγμάτων και ανίχνευσης ακμών. Στην επεξεργασία εικόνας έχουν αναπτυχθεί προχωρημένες τεχνικές για τέτοια και για άλλου είδους επεξεργασία.
function Edges(jpegIn,jpegOut,tau) % jpegin is a string that specifies the % address of a jpeg file that encodes an % image I. % tau is a threshold value between 0 and 255. % Builds an image of I's edges and stores it % as a jpeg file whose address is specified % by the string jpegout. % Convert the jpegin to a grayscale matrix... A = rgb2gray(imread(jpegin)); % Visit each pixel and see if the rate of % change is above the threshold...
[m,n] = size(a); Rho = zeros(m,n,'uint8'); for i = 1:m for j = 1:n % The 3-by-3 matrix of neighbors... Neighbors = A(max(1,i-1):min(i+1,m), max(1,j-1):min(j+1,n)); % Color white those pixels above the % threshold... if max( max( abs( double(neighbors) - double( A(i,j))))) > tau Rho(i,j) = 255; end end end imwrite(rho,jpegout,'jpg')
% Script Eg12_4 % Illustrates median filtering and edge % detection. close all % Acquire and show the original color image... A = imread([pwd '\InsightData\12_4\Cornell_Clock.jpg'],'jpg'); imshow(a) % Turn into black-and-white, display, and save B = rgb2gray(a); figure; imshow(b) imwrite(b,[pwd '\MyData\Cornell_Clock_Gray.jpg'],'jpg')
% Extract a portion of the black-and-white % image, add some noise, and observe the % effect of median filtering figure C = B(850:1150,2350:2650); for k=1:200 i = floor(1+rand(1)*299); j = floor(1+rand(1)*299); C(i:i+1,j:j+1) = floor(10*rand(2,2)); end imshow(c) figure D = MedianFilter(C); imshow(d)
% Find and display the edges in the black- % and-white image... figure jpegin = [pwd '\InsightData\12_4\Cornell_Clock.jpg']; jpegout = [pwd '\MyData\Cornell_Clock_Edges.jpg']; for tau = 30:10:50 Edges(jpegIn,jpegOut,tau); imshow(imread(jpegout)) title(sprintf('tau = %2d',tau),'Fontsize',14) pause end
Tau = 30
Tau = 40
Tau = 50
Επισκόπηση Matlab Ο Τύπος Δεδομένων uint8 Ο τύπος αυτός αναπαράστασης των αριθμών είναι ιδανικός σε περιπτώσεις όπου η τιμή ενός pixel είναι ακέραιος μεταξύ 0 και 255. Ο πίνακας uint8(zeros(m,n)) απαιτεί mn byte αποθηκευτικού χώρου, ενώ ο zeros(m,n) απαιτεί τον οκταπλάσιο. Χρειάζεται μεγάλη προσοχή στους υπολογισμούς με μεταβλητές τύπου uint8. Αν το x είναι τύπου uint8, τότε το x=1000 εκχωρεί στο x το 255, την μέγιστη δυνατή τιμή uint8.
Επισκόπηση Matlab Τρισδιάστατοι Πίνακες Οι πίνακες αυτοί έχουν γραμμές, στήλες και επίπεδα και για την αναφορά στα στοιχεία τους απαιτούνται τρεις δείκτες. Το Α(2,6,3) για παράδειγμα, αναφέρεται στο στοιχείο (2,6) του επιπέδου 3. Η εντολή A=zeros(m,n,p) δημιουργεί τον πίνακα Α ως έναν πίνακα mxmxp. Η δήλωση M=A(:,:,k) εκχωρεί στο Μ το επίπεδο k του Α (έναν πίνακα δηλαδή).
Επισκόπηση Matlab Μετατροπή Πινάκων σε Διανύσματα-Στήλη Αν ο Α είναι ένας πίνακας με m γραμμές και n στήλες, τότε το v = A(:) εκχωρεί στο v ένα διάνυσμα-στήλη, μήκους mn που προκύπτει τοποθετώντας τις στήλες του πίνακα, την μια κάτω από την άλλη: v = [] for k=1:n v = [v; A(:,k)]; end
Επισκόπηση Matlab H Συνάρτηση imread Χρησιμοποιείται για να μεταφέρει τις τιμές των pixels μιας εικόνας στο περιβάλλον εργασίας του Matlab. A = imread(πλήρες Μονοπάτι Αρχείου, Τύπος Αρχείου) Ως συνηθισμένοι τύποι αρχείου περιλαμβάνονται τα jpeg, gif και png. Ο πίνακας Α είναι τύπου uint8 και είναι ένας πίνακας με τις τιμές της κλίμακας του γκρι για ασπρόμαυρες εικόνες, ή ένας τρισδιάστατος πίνακας για έγχρωμες εικόνες. Στην 2 η περίπτωση, τα Α(:,:,1), Α(:,:,2), Α(:,:,3), περιέχουν τις τιμές των pixel για το κόκκινο, το πράσινο και το μπλε, αντίστοιχα.
Επισκόπηση Matlab H Συνάρτηση imshow Χρησιμοποιείται για να εμφανίσει μια εικόνα στο τρέχον παράθυρο γραφικών. imshow(πίνακας που περιέχει τις τιμές των pixels) H Συνάρτηση imwrite Δέχεται έναν πίνακα των τιμών των pixels και δημιουργεί ένα αρχείο εικόνας. Imwrite (Πίνακας τιμών των pixels, Πλήρες Μονοπάτι Αρχείου, Μορφή)
Επισκόπηση Matlab H Συνάρτηση rgb2gray Αν ο Α είναι ένας πίνακας mxnx3 με τις τιμές των pixels που αντιπροσωπεύουν μια έγχρωμη εικόνας Ι, τότε το A_gray=rgb2gray(A) εκχωρεί στο A_gray έναν πίνακα τιμών της κλίμακας του γκρι που αντιστοιχούν στην ασπρόμαυρη έκδοση της εικόνας Ι.
Το ρολόι Big Ben Το πρόβλημα Χτυπάει 1 η ώρα
Το πρόβλημα 0.6 0.4 0.2 0-0.2-0.4-0.6-0.8 0 2 4 6 8 10 12 14 16 18 20 Ο ήχος του ρολογιού είναι μια συνεχής κυματομορφή. Βλέπουμε τα αρχικά χτυπήματα των καμπάνων, την σιωπή στο 15 sec και το γκονγκ λίγο πριν το 17 sec.
Το πρόβλημα 0.15 0.1 0.05 0-0.05-0.1 19 19.01 19.02 19.03 19.04 19.05 19.06 19.07 19.08 19.09 19.1 Εάν παρατηρήσουμε το σήμα του ήχου σε πολύ μικρό χρονικό διάστημα, μπορούμε να διακρίνουμε επαναλήψεις μοτίβων.
Το πρόβλημα Όπως στην περίπτωση της συνεχούς εικόνας, μπορούσαμε να την ψηφιοποιήσουμε με τακτική λήψη δειγμάτων (pixels) και αριθμητική αναπαράσταση (τιμές rgb), έτσι μπορούμε να μετατρέψουμε κι ένα ακουστικό σήμα σε ψηφιακή μορφή, αρκεί τα δείγματα που θα πάρουμε να είναι αρκετά, για να έχουμε πιστή αποτύπωση του αρχικού ήχου.
Το πρόβλημα Τα δείγματα λαμβάνονται ανά τακτά χρονικά διαστήματα (συχνότητα δειγματοληψίας).
Το πρόβλημα Πολύ αργή Επαρκής Η συχνότητα δειγματοληψίας πρέπει να υπακούει στο θεώρημα του Shannon, δηλαδή θεωρητικά πρέπει να είναι τουλάχιστον 2/πλάσια της υψηλότερης αρμονικής του σήματος. Στην πράξη πρέπει να είναι πολλαπλάσια.
Το πρόβλημα Εάν για τον ήχο του Big Ben πάρουμε 8.000 δείγματα ανά δευτερόλεπτο θα έχουμε μια ψηφιοποιημένη έκδοση σε ένα διάνυσμα BigBenVec 19,95x8.000 160.000. Ένα τμήμα του διανύσματος αυτού θα είναι π.χ.: BigBenVec(20004:20008)= -0.0547-0.0156 0.0547 0.0391-0.0391 Ένα τέτοιο διάνυσμα που βρίσκεται στον χώρο εργασίας του Matlab μπορούμε να το επεξεργαστούμε, να το ακούσουμε και να το αποθηκεύσουμε για μελλοντική χρήση.
Το πρόβλημα Εάν υποθέσουμε πως έχουμε ένα τέτοιο διάνυσμα «ήχου» BigBen1 που κωδικοποιεί τον ήχο: «1 η ώρα», γράψτε ένα πρόγραμμα που θα χρησιμοποιεί το διάνυσμα BigBen1 για να δημιουργήσει αρχεία BigBen2, BigBen3,. BigBen12, που θα κωδικοποιούν τα μηνύματα «2 η ώρα», «3 η ώρα», κτλ. Τα αρχεία ήχου, έχουν πολλές κωδικοποιήσεις. Η πιο συνήθης είναι αυτή του.wav. Το αρχείο BigBen.wav, βρίσκεται στον υποκατάλογο \InsightData\13_1. Μπορούμε να το ανοίξουμε και να το διαβάσουμε, ορίζοντας παράλληλα και τις χαρακτηριστικές μεταβλητές του, με: fname = [pwd '\InsightData\13_1\BigBen.wav']; [OneOclock,rate,nBits] = wavread(fname);
OneOclock: Ένα διάνυσμα-στήλη που αποθηκεύει τις τιμές των δειγμάτων της ηχογράφησης. rate: Είναι η συχνότητα δειγματοληψίας με την οποία έχει δημιουργηθεί το διάνυσμα, από το συνεχές σήμα. Π.χ. 8000 σημαίνει πως έχουμε 8000 δείγματα ανά δευτερόλεπτο (8 KHz). nbits: Δηλώνει των αριθμό των bits που χρησιμοποιούνται για την αναπαράσταση κάθε ηχητικού δείγματος. Μια τυπική τιμή για το nbits είναι το 8 και σημαίνει 8 bit. Γενικότερα η δειγματοληψία στα αρχεία ήχου, κυμαίνεται ανάλογα με την εφαρμογή. Στην τηλεφωνία είναι αρκετά τα 8 KHz, στα αρχεία Mp3 44.1 KHz και στα αρχεία υψηλής ευκρίνειας (High Definition) 192.4 KHz.
Σε ένα αρχείο.wav οι αριθμοί αποθήκευσης των δειγμάτων είναι ακέραιοι των 8 bit, πράγμα που επιτρέπει την αναπαράσταση με έως 256 δυνατές τιμές: (-128, -127, 126, 127). Όταν η εντολή wavread διαβάζει το αρχείο.wav άνυσμα-στήλη, μετατρέπει τους ακέραιους σε πραγματικούς και προσαρμόζει την κλίμακα έτσι ώστε να εμπίπτουν στο διάστημα [-1, 1]. Εάν π.χ. ένα δείγμα έχει την τιμή -0.0547, τότε στο αρχείο BigBen1.wav αποθηκεύεται ως ακέραιος -7=-0.0547*128. Στα αρχεία.wav δεν χρειάζεται να ασχοληθούμε με τέτοιες λεπτομέρειες και η παράμετρος εξόδου nbits της εντολής wavread μπορεί να αγνοηθεί επίσης.
Το ακόλουθο script διαβάζει και εμφανίζει την σχετική κυματομορφή BigBen.wav. fname = [pwd '\InsightData\13_1\BigBen.wav']; [OneOclock,rate] = wavread(fname); % Αριθμός δειγμάτων n = length(oneoclock); % Διάρκεια ηχογράφησης (σε δευτερόλεπτα) T = (n-1)/rate; % Δειγματοληπτικοί χρόνοι tvals = linspace(0,t,n); % Γραφική παράσταση plot(tvals,oneoclock) Για να το ακούσουμε χρησιμοποιούμε την εντολή sound: sound(oneoclock,rate)
Η συνάρτηση sound έχει δύο παραμέτρους εισόδου. Η 1 η είναι το διάνυσμα του ήχου και η 2 η ο ρυθμός αναπαραγωγής του, ο οποίος είναι σχεδόν πάντα ο ίδιος μ αυτόν που μας έδωσε ή wavread. Μπορούμε βέβαια να τον αλλάξουμε αν θέλουμε. Π.χ. εάν θέσουμε: sound(oneoclock,2*rate) Ο ήχος θα παιχτεί σε 2/πλάσιο ρυθμό απ αυτόν της ηχογράφησης και θα ακουστεί σε πιο υψηλό τόνο. Για το πρόγραμμα που θέλουμε να αναπτύξουμε, πρέπει πρώτα να ξεχωρίσουμε από το αρχικό αρχείο τους ήχους των καμπάνων από τον ήχο του γκονγκ και να τα αποθηκεύσουμε σε δύο διαφορετικά διανύσματα: chimes και gong αντίστοιχα.
Εάν έχουμε αυτά τα δύο αρχεία, τότε μπορούμε να δημιουργήσουμε και να παίξουμε την ηχογράφηση: «2 η ώρα»: TwoOclock = [chimes; gong; gong]; sound(twooclock,rate) Στην συνέχεια «3 η ώρα»: ThreeOclock = [TwoOclock; gong]; sound(threeoclock,rate) Είναι προφανές πως με τον τρόπο αυτό, μπορούμε να κατασκευάσουμε τους ήχους όλων των ωρών και να τους αποθηκεύσουμε. Προς τούτο χρησιμοποιείται η εντολή wavwrite, η οποία είναι μια εσωτερική συνάρτηση του Matlab και χρειάζεται συνολικά:
Το διάνυσμα με τα ηχητικά δείγματα, τον ρυθμό δειγματοληψίας, τον αριθμό των bit για την αναπαράσταση (πάντα 8), καθώς και το όνομα του αρχείου αποθήκευσης. Με το script: fname = [pwd '\MyData\BigBen2.wav']; wavewrite(twooclock, 8000, 8, fname) Δημιουργείται ένα αρχείο που ονοματίζεται BigBen2.wav και κωδικοποιεί την ηχογράφηση «2 η ώρα». Η συνολική επίλυση του αρχικού προβλήματος θα είναι:
% Script Eg13_1 % Δημιουργεί αρχεία ήχου για καθένα από τα 12 % χτυπήματα του BigBen με δεδομένα από το αρχείο % BigBen.wav. close all % Δάβασε το αρχείο ήχου για το χτύπημα «1 η ώρα»... fname = [pwd '\InsightData\13_1\BigBen.wav']; [OneOclock,rate] = wavread(fname); n = length(oneoclock); % Εμφάνισε την κυματομορφή και κάνε κλικ στην περιοχή % ανάμεσα στα κουδουνίσματα και στο γκονγκ... plot(oneoclock) title('click at the beginning of the gong.') [m,y] = ginput(1); m = round(m); Chimes = OneOclock(1:m); Gong = OneOclock(m+1:n);
% Για κάθε ωριαίο χτύπημα, δημιούργησε ένα αρχείο.wav % και ονόμασέ τα BigBen1, BigBen2,...,BigBen12. F = Chimes; for k=1:12 F = [F; Gong]; fname = [pwd '\MyData\BigBen' num2str(k) '.wav']; wavwrite(f,rate,8,fname) end % Παίξε ένα επιλεγμένο υποσύνολο των ηχογραφήσεων... PlayList = [2 3]; for k = PlayList fname = [pwd '\MyData\BigBen' num2str(k) '.wav']; [Oclock,rate] = wavread(fname); sound(oclock) end
Το πρόβλημα της κατάτμησης Για την κατάτμηση του αρχείου ήχου BigBen, κάνουμε κλικ πάνω στο ηχητικό κύμα μεταξύ του τμήματος των καμπάνων και του τμήματος του γκονγκ. Στην επεξεργασία σήματος, το πρόβλημα της κατάτμησης είναι ένα σημαντικό και δύσκολο κεφάλαιο, γενικότερα. Π.χ. σε ένα σύστημα αναγνώρισης ομιλίας για τον διαχωρισμό των λέξεων πρέπει να βρεθεί το σημείο εκείνο που τις διαχωρίζει. Πότε τελειώνει μια λέξη και πότε ξεκινάει η άλλη; Στο αρχείο BigBen οι καμπάνες έχουν 16 χτυπήματα, οργανωμένα σε 4 ομάδες. Μπορούμε να εντοπίσουμε, στο ηχητικό κύμα, κάποια από τα χτυπήματα, αλλά η χρονική κλίμακα είναι τόσο συμπιεσμένη που δεν μπορούμε να τα εντοπίσουμε όλα.
Το πρόβλημα της κατάτμησης Είναι σχετικά εύκολο να μεγεθύνουμε την κλίμακα του χρόνου εξετάζοντας υποδιανύσματα του OneOclock και να καταφέρουμε να απομονώσουμε ορισμένα χτυπήματα της καμπάνας. Πως μπορεί όμως να αυτοματοποιηθεί ένας τέτοιος διαχωρισμός; Πως μπορεί κατ αρχή να διαχωριστεί ένα χρονικό διάστημα ησυχίας σε μια ηχογράφηση; Εάν για παράδειγμα θέταμε μια εντολή συνθήκης: max(abs(oneoclock(l:r)))<=tol Εάν αυτή είναι αληθής για μια μικρή τιμή ανοχής tol, αυτό θα σημαίνει πως επικρατεί ησυχία μεταξύ των δύο σημείων L και R.
Το πρόβλημα της κατάτμησης Εάν όμως είχαμε μέσα σ αυτό το διάστημα έναν τυχαίο θόρυβο, αυτή η εντολή θα αποτύγχανε. Απαιτείται επομένως κάποιο είδος φιλτραρίσματος πριν προχωρήσουμε στην καθεαυτό επεξεργασία των τμημάτων της ηχογράφησης, για να καταλήξουμε σε συμπεράσματα και διαπιστώσεις σε σχέση με τα ανωτέρω.
Επισκόπηση Matlab wavread Χρησιμοποιείται για την ανάκτηση δεδομένων από ένα αρχείο.wav. Σε γενική μορφή συντάσσεται: [Διάνυσμα Ήχου, Ρυθμός Δειγματοληψίας, Ακρίβεια] = wavread (Όνομα Αρχείου) Δηλαδή η: [SoundVec,rate,nBits] = wavread ( MySoundFile.wav ) Εκχωρεί τις τιμές των δειγμάτων στο διάνυσμα-στήλη SoundVec, τον ρυθμό δειγματοληψίας (σε Hertz) στο rate και το μήκος της bit κωδικοποίησης που χρησιμοποιήθηκε (συνήθως 8) στο nbits. To nbits μπορεί και να παραληφθεί εντελώς από την εντολή, καθώς εξυπακούεται τότε το 8.
Επισκόπηση Matlab sound Χρησιμοποιείται για να παίξει τον ήχο που είναι κωδικοποιημένος στο διάνυσμα ήχου. Σε γενική μορφή συντάσσεται: sound(διάνυσμα Ήχου, Ρυθμός Δειγματοληψίας) Δηλαδή η: sound(mysoundvec, MyRate) Θα παίξει τον κωδικοποιημένο ήχο MySoundVec σε ρυθμό MyRate.
Επισκόπηση Matlab wavwrite Χρησιμοποιείται για την δημιουργία ενός αρχείου.wav. Σε γενική μορφή συντάσσεται: wavwrite(διάνυσμα Ήχου, Ρυθμός Δειγματοληψίας, Ακρίβεια, Όνομα Αρχείου) Δηλαδή η: wavwrite(mysoundvec, 8000, 8, MySoundFile.wav ) Κατασκευάζει το αρχείο.wav που το ονομάζει MySoundFile στο οποίο αποθηκεύει τα ακουστικά δεδομένα του MySoundVec.
Το πρόβλημα Η διακύμανση της συνάρτησης y t = sin 2πft ως προς τον χρόνο, εξαρτάται από την τιμή της συχνότητας f.
Το πρόβλημα Η διακύμανση της συνάρτησης y t = sin 2πft ως προς τον χρόνο, εξαρτάται από την τιμή της συχνότητας f.
Το πρόβλημα Στοιχειώδης ημιτονοειδείς συναρτήσεις μπορούν να συνδυαστούν και να κλιμακωθούν για την παραγωγή πιο περίπλοκων σημάτων:
Το πρόβλημα Στα τηλέφωνα που λειτουργούν τονικά, κάθε πάτημα του πλήκτρου παράγει ένα σήμα που είναι το άθροισμα δύο στοιχειωδών ημιτονοειδών συναρτήσεων: 697 770 852 941 1 GHI 4 PQRS 7 * ABC 2 JKL 5 TUV 8 0 DEF 3 MNO 6 WXYZ 9 # 1209 1336 1477 Με το πάτημα ενός πλήκτρου παράγεται ένα σήμα που είναι συνδυασμός των δύο συχνοτήτων, οριζοντίως και καθέτως, που δίνει σε κάθε πλήκτρο το δικό του αποτύπωμα. Π.χ. με το πάτημα του πλήκτρου 6, παράγεται ένα σήμα y t που είναι συνδυασμός των δύο συχνοτήτων: 770 και 1447: y t = 1 2 sin 2πω rowt + 1 2 sin 2πω colt
Πλήκτρο 2 1 0.5 0-0.5-1 Πλήκτρο 4 1 0.5 0-0.5-1 Πλήκτρο 0 1 0.5 0-0.5-1 Original Signal( Row = 1, Col = 2 ) 50 100 150 200 250 300 Original Signal( Row = 2, Col = 1 ) 50 100 150 200 250 300 Original Signal( Row = 4, Col = 2 ) 50 100 150 200 250 300
Το πρόβλημα Αν και οι κυματομορφές φαίνονται οπτικά περίπου ίδιες, στην πραγματικότητα είναι πολύ διαφορετικές, πράγμα που επιτρέπει στον δέκτη την ακριβή κωδικοποίηση του εισερχόμενου σήματος, ακόμα και με την παρουσία θορύβου. Οι δύο κυματομορφές (με θόρυβο), ανήκουν στα πλήκτρα 2 και 6. 1 0.5 0-0.5-1 Received Signal 1 0.5 0-0.5-1 Received Signal
Το πρόβλημα Θα φτιάξουμε κατ αρχή μια συνάρτηση, την MakeShowPlay, η οποία θα παίζει και θα εμφανίζει για ¼ του sec την κυματομορφή και τον ήχο που αντιστοιχεί στο πάτημα ενός πλήκτρου. Το ψηφιοποιημένο σήμα είναι ένα διάνυσμα και πρέπει για να το παίξουμε να ορίσουμε και τον ρυθμό αναπαραγωγής του. Η δειγματοληψία του σήματος είναι 2 15 = 32768/sec, δηλαδή κατά την διάρκεια ενός sec πρέπει να πάρουμε 32768 δείγματα ακριβώς. Για να δημιουργήσουμε και να παίξουμε τον ήχο, για ¼ του δευτερολέπτου, θα έχουμε:
Fs = 32768; % Duration of the tone is.25 sec. tvals = (0:(1/Fs):.25)'; Ορίζει την διάρκεια του τονικού παλμού και την δειγματοληψία % Sample the signal sin(2*pi*fr(i)*t) + sin(2*pi*fc(j)*t) at times % specified by tvals and add in noise... yr = sin(2*pi*fr(i)*tvals); yc = sin(2*pi*fc(j)*tvals); y = (yr + yc)/2; Δημιουργεί τον παλμό, ως συνδυασμό των δύο σημάτων (οριζοντίως & καθέτως), με πλάτος τον μέσο όρο τους
Το πρόβλημα Ο συνδυασμός των δύο ψηφιακών σημάτων ισοδυναμεί με συνδυασμό των δύο διανυσμάτων τους. Καθώς τα διανύσματα είναι αρκετά μεγάλα, είναι λογικό να εμφανίσουμε ένα μικρό τμήμα τους, π.χ. y(1:300). Με την συνάρτηση sound θα παίξουμε τον ήχο, έχοντας παραμέτρους εισόδου το διάνυσμα και τον ρυθμό αναπαραγωγής του.
function [tvals,y] = MakeShowPlay(i,j) % i (1<=i<=4) and j (1<=j<=3) are integers that identify % the row i and column j button in the touch-tone pad. % Displays the associated wave form and plays the tone. % The sample times and signal are returned in tvals and y. % The touchpad frequencies for the rows and columns... fr = [ 697 770 852 941]; fc = [ 1209 1336 1477]; % Sampling rate. This many numbers/sec in the digital signal: Fs = 32768; % Duration of the tone is.25 sec. tvals = (0:(1/Fs):.25)'; % Sample the signal sin(2*pi*fr(i)*t) + sin(2*pi*fc(j)*t) at % times specified by tvals and add in noise... yr = sin(2*pi*fr(i)*tvals); yc = sin(2*pi*fc(j)*tvals); y = (yr + yc)/2;
% Display a representative part of the waveform... figure set(gcf,'position',[100 550 800 160]) m = 300; plot((1:m)',y(1:m),(1:m)',zeros(1,m),'-r') axis([1 m -1.2 1.2]) title(sprintf('original Signal( Row = %1d, Col = %1d ),i,j),'fontsize',14) shg % Play the tone... pause(1) sound(y,fs)
Το πρόβλημα Για να προσομοιώσουμε τον θόρυβο στα τονικά σήματα, μπορούμε να χρησιμοποιήσουμε την συνάρτηση randn. Στην συνάρτηση SendNoisy προσθέτουμε σε κάθε ψηφιοποιημένη τιμή του σήματος έναν τυχαίο αριθμό από την κανονική κατανομή. Το σήμα που λαμβάνουμε εμπεριέχει την πληροφορία μαζί με τον θόρυβο. Το ζητούμενο είναι να αποκρυπτογραφήσουμε το ψηφιοποιημένο σήμα και να προσδιορίσουμε το πλήκτρο που πατήθηκε.
Πρέπει να έχουμε στην διάθεσή μας τα ιδανικά σήματα, σε δύο πίνακες (truer, truec) που αντιστοιχούν στις συχνότητες των γραμμών και των στηλών του πληκτρολογίου: Fs = 32768; tvals = (0:(1/Fs):.25); Tau = 2*pi*tVals ; fr = [ 697 770 852 941]; truer = [sin(tau*fr(1)) sin(tau*fr(2))... sin(tau*fr(3)) sin(tau*fr(4))]; fc = [ 1209 1336 1477]; truec = [sin(tau*fc(1)) sin(tau*fc(2))... sin(tau*fc(3))];
function ynoisy = SendNoisy(tVals,y) % Adds noise to the signal y. % Displays and plays the resulting signal ynoisy n = length(y); % Add noise... A =.5; ynoisy = y + A*randn(n,1); % Display a representative part of the sinusoid... figure set(gcf,'position',[100 300 800 160]) m = 300; plot((1:m)',ynoisy(1:m),(1:m)',zeros(m,1),'-r') axis([1 m -1.2 1.2]) set(gca,'xticklabel','') title('received Signal','Fontsize',14) shg % Play the noisy tone... Fs = 32768; pause(1) sound(ynoisy,fs)
Ένα βασικό χαρακτηριστικό των διανυσμάτων τέλειου σήματος είναι πως είναι πολύ διαφορετικά μεταξύ τους, δηλαδή πως υπάρχει πολύ μικρή συσχέτιση μεταξύ τους. Ένας τρόπος για να μετρήσουμε την συσχέτιση που υπάρχει μεταξύ δύο διανυσμάτων-στήλες x και y, με n στοιχεία, είναι να υπολογίσουμε το συνημίτονο της μεταξύ τους γωνίας: cos xy = i=1 n x i y i i=1 n x 2 n i i=1 y 2 i Η σχέση αυτή είναι όντως ένα συνημίτονο. Έστω ότι είναι: x = cos θ 1 sin θ 1 και y = cos θ 2 sin θ 2 Ισχύει ότι: cos xy = cos θ 1 cos θ 2 + sin θ 1 sin θ 2 = cos θ 1 θ 2 Αν θ 1 = 45 ο και θ 2 = 60 ο τότε τα x και y σχηματίζουν γωνία 15 ο.
Όταν η ποσότητα cos xy είναι κοντά στο 1, αυτό σημαίνει πως τα τα x και y συσχετίζονται πολύ και άρα είναι παραπλήσια. Όταν είναι κοντά στο 0, αυτό σημαίνει πως τα x και y δεν έχουν καμιά σχέση μεταξύ τους. Π.χ. εάν: x = 3,1 4,9 x = 3,1 και y = 2,9 5,1 cos xy = 0,9988 και y = 5,1 2,9 cos xy = 0,0470 4,9 Στην 1 η περίπτωση έχουμε ισχυρή συσχέτιση, ενώ στην 2 η μηδαμινή. function c = cos_xy(x,y) % x and y are column n-vectors % c is the cosine of the angle between them c = abs(sum(x.*y))/(sqrt(sum(x.*x))*sqrt(sum(y.*y)));
Αν εξετάσουμε τις συσχετίσεις ανάμεσα στα διανύσματα τέλειου σήματος με τα δεδομένα που έχουμε ήδη δημιουργήσει στους πίνακες truer, truec: A =[truer truec]; CosVals = zeros(7,7); for i=1:7 for j=1:7 CosVals(i,j)=cos_xy(A(:,i),A(:,j)); end end Θα πάρουμε τις εξής συσχετίσεις: CosVals = 1.0000 0.0092 0.0045 0.0092 1.0000 0.0000 0.0045 0.0000 1.0000 0.0001 0.0034 0.0068 0.0001 0.0011 0.0015 0.0013 0.0000 0.0000 0.0001 0.0001 0.0013 0.0034 0.0011 0.0000 0.0068 0.0015 0.0000 1.0000 0.0001 0.0019 0.0001 1.0000 0.0053 0.0019 0.0053 1.0000 0.0001 0.0006 0.0007 0.0001 0.0001 0.0043 0.0001 0.0006 0.0007 0.0001 0.0001 0.0043 1.0000
Εξετάζοντας τα στοιχεία του πίνακα, βλέπουμε πως τα διαγώνια στοιχεία είναι 1, γιατί έχουμε συσχέτιση ενός τέλειου σήματος με τον εαυτό του. Αντιθέτως οι συσχετίσεις -όπου αλλού- είναι μηδαμινές, πράγμα που σημαίνει πως τα επτά σήματα είναι πολύ διαφορετικά μεταξύ τους. Στην συνέχεια θα δούμε την συσχέτιση ενός σήματος που έχει αλλοιωθεί από τον θόρυβο, με τα 7 τέλεια σήματα. Εάν το πλήκτρο που πατήθηκε βρίσκεται στην σειρά i και στην στήλη j του πληκτρολογίου, τότε: 1. Ανάμεσα στα 4 τέλεια σήματα στο truer, το σήμα εισόδου θα πρέπει να έχει την ισχυρότερη συσχέτιση με το truer(:,i). 2. Ανάμεσα στα 3 τέλεια σήματα στο truec, το σήμα εισόδου θα πρέπει να έχει την ισχυρότερη συσχέτιση με το truec(:,j).
Η συνάρτηση ShowCosines διερευνά αυτές τις υποθέσεις χρησιμοποιώντας ραβδογράμματα. Το τελικό πρόγραμμα καλεί την συνάρτηση ShowCosines για 10 διαφορετικά, τυχαία «πατήματα πλήκτρων». function ShowCosines(y) % Shows the cosine that signal y makes with each of the % 4 perfect row signals and each of the three perfect % column signals. % Set up the perfect signal matrices... Fs = 32768; tvals = 0:(1/Fs):.25; tau = 2*pi*tVals'; fr = [697 770 852 941]; truer = [sin(tau*fr(1)) sin(tau*fr(2)) sin(tau*fr(3)) sin(tau*fr(4))]; fc = [1209 1336 1477]; truec = [sin(tau*fc(1)) sin(tau*fc(2)) sin(tau*fc(3))];
% Compute the row and column cosines... for i=1:4 rowcosine(i) = cos_xy(y,truer(:,i)); end for i=1:3 colcosine(i) = cos_xy(y,truec(:,i)); end % Display... figure set(gcf,'position',[100 50 800 160]) subplot(1,2,1) bar(rowcosine) title('row Cosines','Fontsize',14) axis([0 5 0 1.1*max(rowCosine)]) set(gca,'xtick',[1 2 3 4]) subplot(1,2,2) bar(colcosine) title('column Cosines','Fontsize',14) axis([0 5 0 1.1*max(colCosine)]) set(gca,'xtick',[1 2 3])
% Script Eg13_2 % Examines the Touch-tone system in the % presence of noise. for Trial = 1:10 close all % Choose a button at random... i = ceil(rand*4); j = ceil(rand*3); % Generate and the tone... [tvals,y] = MakeShowPlay(i,j); % "Send" a noisy version... y = SendNoisy(tVals,y); % "Receive" and decipher... ShowCosines(y) pause(2) end
1 0.5 0-0.5-1 Original Signal( Row = 1, Col = 3 ) 50 100 150 200 250 300 1 0.5 0-0.5-1 Received Signal Row Cosines Column Cosines 0.4 0.4 0.2 0.2 0 1 2 3 4 0 1 2 3
Ανασκόπηση Matlab Εσωτερικά Γινόμενα και Νόρμες Αν x και y είναι διανύσματα-στήλη με το ίδιο μήκος, τότε το s = x *y ισοδυναμεί με το s = sum(x.*y). Δηλαδή: s = 0; for i=1:length(x) s = s+x(i)*y(i); end Αυτή η πράξη λέγεται και εσωτερικό γινόμενο μεταξύ x και y. Η συνάρτηση norm μπορεί να χρησιμοποιηθεί για τον υπολογισμό της s = sqrt(sum(x.*x) : s = norm(x) Η υλοποίηση της cos_xy περιλαμβάνει ένα εσωτερικό γινόμενο και δύο υπολογισμούς με την norm.