Kurs içeriği (10 / 10)
  1. 1 Raspberry Pi ile Tanışma ve Kurulum 80 dk
  2. 2 Makine Öğrenmesi Nedir? İlk Modelimiz 80 dk
  3. 3 Kamera ve Görüntü İşlemenin Temelleri 80 dk
  4. 4 Görüntü Veri Seti Hazırlama 80 dk
  5. 5 Görüntü Tanıma Modeli Eğitip Pi’de Çalıştırma 120 dk
  6. 6 Mikrofon ve Sesin Temelleri 80 dk
  7. 7 Ses Veri Seti Hazırlama 80 dk
  8. 8 Ses Tanıma Modeli ile Sesli Komutlar 120 dk
  9. 9 Yapay Zekâyı Donanıma Bağlama: LED, Buzzer ve Servo 80 dk
  10. 10 Mini Proje: Akıllı Geri Dönüşüm Kutusu 160 dk

Raspberry Pi ile Makine Öğrenmesi: Görüntü ve Ses Tanıma · Ders 10 / 10

Mini Proje: Akıllı Geri Dönüşüm Kutusu

Proje ⏱ 160 dk

Kurs boyunca öğrendiklerimizi tek bir projede birleştiriyoruz: “aç” komutuyla kapağını açan, kameraya gösterilen atığı kâğıt, plastik ya da metal olarak tanıyan, sonucu LED ve buzzer ile bildirip kayıt dosyasına yazan akıllı bir geri dönüşüm kutusu. Son olarak modeli bir test oturumuyla deniyor, doğruluğu ve karışıklık matrisini hesaplıyoruz.

Bu derste

  • Görüntü ve ses modellerini, sensörleri ve eyleyicileri tek bir sistemde birleştirir.
  • Bir projeyi durumlar (kapalı, açık) ve geçişlerle planlar.
  • Sistemin kararlarını zaman bilgisiyle CSV dosyasına kaydeder.
  • Bir test oturumu düzenleyerek modelin doğruluğunu ölçer ve karışıklık matrisini yorumlar.
  • Projesini tanıtır, sınırlılıklarını ve geliştirme fikirlerini açıklar.

Malzemeler

  • Raspberry Pi 5 veya 4, kamera ve USB mikrofon
  • Ders 9’daki devre (2 LED, buzzer, SG90 servo)
  • Ders 5’teki görüntü modeli ve Ders 8’deki ses modeli (model_ses.eim)
  • Karton kutu, maket bıçağı, sıcak silikon veya bant
  • Test için hiç fotoğraflanmamış atık örnekleri

Dokuz derstir parça parça hazırladığımız her şeyi şimdi bir araya getiriyoruz. Ortaya çıkacak olan, sınıfınızda gerçekten kullanabileceğiniz bir cihaz: Sesinize cevap veren, attığınız atığı tanıyan ve her şeyi kaydeden akıllı bir geri dönüşüm kutusu.

Dersin Amacı ve Kazanımlar

Bu dersin sonunda:

  1. Görüntü ve ses modellerini, sensörleri ve eyleyicileri tek bir sistemde birleştirebileceksiniz.
  2. Bir projeyi durumlar ve geçişlerle planlayabileceksiniz.
  3. Sistemin kararlarını CSV dosyasına kaydedebileceksiniz.
  4. Bir test oturumuyla modelin doğruluğunu ölçüp karışıklık matrisini yorumlayabileceksiniz.
  5. Projenizi tanıtıp sınırlılıklarını ve geliştirme fikirlerini açıklayabileceksiniz.

Gerekli Malzemeler

Malzeme Açıklama
Raspberry Pi, kamera, USB mikrofon Önceki derslerde kullandığınız düzenek
Ders 9’daki devre Yeşil LED GPIO17, kırmızı LED GPIO27, buzzer GPIO22, servo GPIO18
Modeller model/ klasörü ve goruntu_model.py (Ders 5), model_ses.eim (Ders 8)
Kutu malzemeleri Karton kutu, kapak için hafif bir karton parça, servo kolunu kapağa bağlamak için tel ya da çubuk, bant ve sıcak silikon (öğretmen gözetiminde)
Test atıkları Ders 4’te kenara ayırdığınız, modelin hiç görmediği nesneler

💡 İpucu

Projeyi tek seferde çalıştırmaya çalışmayın. Önce kutuyu ve servo kapağı yapın, donanim_test.py ile deneyin. Sonra sesli komutu, en son görüntü tanımayı ekleyin. Her adım çalıştıktan sonra bir sonrakine geçmek, hatanın nerede olduğunu bulmayı çok kolaylaştırır.

Proje Senaryosu

  1. Kutunun kapağı kapalıdır, kutu sizi dinler.
  2. “Aç” dediğinizde servo kapağı açar.
  3. Atığı kapağın altındaki kameraya gösterirsiniz. Model atığı tanır: Yeşil LED yanar, buzzer kâğıt için 1, plastik için 2, metal için 3 kez öter ve sonuç zamanıyla birlikte kutu_kaydi.csv dosyasına yazılır.
  4. Model emin değilse kırmızı LED yanar; atığı biraz çevirmeniz gerekir.
  5. “Kapat” dediğinizde ya da 20 saniye boyunca hiçbir şey olmazsa kapak kapanır.

Akıllı geri dönüşüm kutusunun sistem mimarisi

Durumlarla Düşünmek

Karmaşık bir programı planlamanın iyi bir yolu, sistemin hangi durumlarda bulunabileceğini ve bir durumdan diğerine neyle geçtiğini çizmektir. Kutumuzun iki durumu var: KAPALI ve AÇIK. KAPALI durumda yalnızca “aç” komutu önemlidir. AÇIK durumda kamera çalışır; “kapat” komutu ya da süre dolması kutuyu yeniden KAPALI duruma götürür.

Kutunun durum diyagramı

Kutuyu Yapalım

  1. Karton kutunun üst yüzüne atık atma deliği açın ve deliği örtecek hafif bir kapak hazırlayın. Kapağın bir kenarını bantla menteşe gibi tutturun.
  2. Servoyu kutunun iç yan duvarına, kolu kapağı aşağıdan itecek şekilde sabitleyin. Servo kolunu kapağa bir tel ya da çubukla bağlayın.
  3. Kamerayı deliğin üstüne, aşağı bakacak şekilde yerleştirin; atık delikten geçmeden önce kameranın görüş alanında dursun. Veri toplarken kullandığınız açıya ve ışığa olabildiğince yakın bir düzen kurun.
  4. LED’leri ve buzzer’ı kutunun ön yüzüne, görünür bir yere yerleştirin. Mikrofonu servodan biraz uzağa koyun; servonun sesi komut sanılmasın.

Akıllı geri dönüşüm kutusu prototipi

Ana Program

Klasörünüzde şu dosyalar bulunmalı: goruntu_model.py, model/ klasörü ve model_ses.eim. Ana program bunları kullanır:

# dosya: akilli_kutu.py  (sesle açılan, atığı tanıyan geri dönüşüm kutusu)
import os
import csv
import time
from picamera2 import Picamera2
from edge_impulse_linux.audio import AudioImpulseRunner
from gpiozero import LED, Buzzer, AngularServo
from goruntu_model import tahmin_et

KLASOR = os.path.dirname(os.path.abspath(__file__))
SES_MODELI = os.path.join(KLASOR, "model_ses.eim")
KAYIT_DOSYASI = os.path.join(KLASOR, "kutu_kaydi.csv")
CIHAZ = None                 # mikrofon numarası (None: program sorar)
SES_ESIK = 0.80
GORUNTU_ESIK = 0.80
KARARLILIK = 3               # aynı görüntü kararı art arda kaç kez gelmeli
ACIK_KALMA = 20              # saniye; işlem olmazsa kapak kendiliğinden kapanır
KAPALI, ACIK = 0, 90         # kapak açıları
BIP = {"kagit": 1, "plastik": 2, "metal": 3}

yesil = LED(17)
kirmizi = LED(27)
buzzer = Buzzer(22)
kapak = AngularServo(18, min_angle=0, max_angle=180,
                     min_pulse_width=0.0005, max_pulse_width=0.0024)


def kapak_ayarla(aci):
    kapak.angle = aci
    time.sleep(0.6)
    if aci == KAPALI:
        kapak.detach()       # kapalıyken servoyu serbest bırak (titreme olmasın)


def kaydet(atik, guven):
    yeni_dosya = not os.path.exists(KAYIT_DOSYASI)
    with open(KAYIT_DOSYASI, "a", newline="", encoding="utf-8") as f:
        yazici = csv.writer(f)
        if yeni_dosya:
            yazici.writerow(["zaman", "tahmin", "guven"])
        yazici.writerow([time.strftime("%Y-%m-%d %H:%M:%S"), atik, f"{guven:.2f}"])


picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(
    main={"size": (640, 480), "format": "RGB888"}))
picam2.start()
kapak_ayarla(KAPALI)

acik = False
acilma = son_komut = 0.0
aday, sayac, son_durum = None, 0, "bos"

with AudioImpulseRunner(SES_MODELI) as runner:
    try:
        runner.init()
        print("Hazır. Kapağı açmak için 'aç' deyin.")
        for sonuc, _ in runner.classifier(device_id=CIHAZ):
            simdi = time.time()
            olasiliklar = sonuc["result"]["classification"]
            etiket = max(olasiliklar, key=olasiliklar.get)
            komut = None
            if etiket in ("ac", "kapat") and olasiliklar[etiket] >= SES_ESIK \
                    and simdi - son_komut > 1.5:
                komut, son_komut = etiket, simdi

            if komut == "ac" and not acik:                       # KAPALI -> AÇIK
                kapak_ayarla(ACIK)
                acik, acilma = True, simdi
                aday, sayac, son_durum = None, 0, "bos"
                print("Kapak açıldı. Atığı kameraya gösterin.")
            elif acik and (komut == "kapat" or simdi - acilma > ACIK_KALMA):   # AÇIK -> KAPALI
                kapak_ayarla(KAPALI)
                acik = False
                yesil.off()
                kirmizi.off()
                print("Kapak kapandı.")
            elif acik:                                            # AÇIK: atığı tanı
                atik, guven, _ = tahmin_et(picam2.capture_array())
                durum = atik if guven >= GORUNTU_ESIK else "belirsiz"
                if durum == aday:
                    sayac += 1
                else:
                    aday, sayac = durum, 1
                if sayac == KARARLILIK and durum != son_durum:
                    son_durum = durum
                    if durum == "belirsiz":
                        yesil.off()
                        kirmizi.on()
                        print("Emin değilim. Atığı biraz çevirin.")
                    elif durum == "bos":
                        yesil.off()
                        kirmizi.off()
                    else:
                        kirmizi.off()
                        yesil.on()
                        buzzer.beep(on_time=0.15, off_time=0.15, n=BIP[durum], background=True)
                        kaydet(durum, guven)
                        acilma = simdi                           # kullanıldıkça açık kalsın
                        print(f"{durum} (%{guven * 100:.0f}) kaydedildi.")
    except KeyboardInterrupt:
        print("Program durduruldu.")
    finally:
        runner.stop()
        picam2.stop()
        kapak_ayarla(KAPALI)

Örnek çıktı: (Raspberry Pi 5 üzerinde örnek çıktı. Mikrofon seçimi ve kütüphanelerin bilgi satırları da görünebilir.)

Hazır. Kapağı açmak için 'aç' deyin.
Kapak açıldı. Atığı kameraya gösterin.
plastik (%95) kaydedildi.
Emin değilim. Atığı biraz çevirin.
metal (%88) kaydedildi.
Kapak kapandı.
^CProgram durduruldu.

Programın kalbi, Ders 8’deki ses döngüsüdür. Ses kütüphanesi her çeyrek saniyede yeni bir sonuç verir. Döngünün her turunda önce bir komut gelip gelmediğine bakarız, sonra duruma göre davranırız: Kapak kapalıysa yalnızca “aç” komutunu bekleriz; kapak açıksa her turda kameradan bir kare alıp modele sorarız. Görüntü kararlarında Ders 9’daki kararlılık fikrini kullanıyoruz: Aynı karar art arda 3 kez gelmeden tepki vermiyoruz. finally bloğu, program nasıl biterse bitsin kapağın kapanmasını ve kameranın, ses modelinin düzgünce kapatılmasını garanti eder.

Kayıt dosyası basit bir tablodur; Excel ya da LibreOffice Calc ile açabilirsiniz:

zaman,tahmin,guven
2026-11-12 10:41:03,plastik,0.95
2026-11-12 10:41:19,metal,0.88

Kutumuz Ne Kadar Başarılı?

“Çalışıyor gibi görünüyor” bir mühendis için yeterli bir cevap değildir. Başarıyı ölçmek için bir test oturumu yapacağız. Ders 4’te kenara ayırdığınız, modelin hiç görmediği atıkları kullanın. Aşağıdaki programda her nesneyi kameraya gösterip gerçek sınıfını yazarsınız; program 5 kare alıp çoğunluk oyuyla tahminini kaydeder.

# dosya: test_kaydi.py  (doğruluk ölçmek için test oturumu)
import csv
import os
import time
from picamera2 import Picamera2
from goruntu_model import tahmin_et

DOSYA = "test_sonuclari.csv"
SINIFLAR = ("kagit", "plastik", "metal", "bos")
KARE_SAYISI = 5          # her nesne için 5 kare alıp çoğunluk oyu kullan

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(
    main={"size": (640, 480), "format": "RGB888"}))
picam2.start()
time.sleep(1)

yeni_dosya = not os.path.exists(DOSYA)
with open(DOSYA, "a", newline="", encoding="utf-8") as f:
    yazici = csv.writer(f)
    if yeni_dosya:
        yazici.writerow(["gercek", "tahmin", "guven"])
    while True:
        gercek = input("Nesnenin gerçek sınıfı (kagit/plastik/metal/bos, bitirmek için q): ").strip().lower()
        if gercek == "q":
            break
        if gercek not in SINIFLAR:
            print("  Geçersiz sınıf adı.")
            continue
        oylar = {}
        for _ in range(KARE_SAYISI):
            etiket, guven, _ = tahmin_et(picam2.capture_array())
            oylar.setdefault(etiket, []).append(guven)
        tahmin = max(oylar, key=lambda e: len(oylar[e]))
        guven = sum(oylar[tahmin]) / len(oylar[tahmin])
        yazici.writerow([gercek, tahmin, f"{guven:.2f}"])
        f.flush()
        isaret = "doğru" if tahmin == gercek else "YANLIŞ"
        print(f"  Tahmin: {tahmin} (%{guven * 100:.0f}) -> {isaret}")

picam2.stop()
print("Sonuçlar", DOSYA, "dosyasına kaydedildi.")

Örnek çıktı: (Raspberry Pi 5 üzerinde örnek çalıştırma; kullanıcının yazdığı değerler de görünüyor.)

Nesnenin gerçek sınıfı (kagit/plastik/metal/bos, bitirmek için q): plastik
  Tahmin: plastik (%93) -> doğru
Nesnenin gerçek sınıfı (kagit/plastik/metal/bos, bitirmek için q): metal
  Tahmin: plastik (%71) -> YANLIŞ
Nesnenin gerçek sınıfı (kagit/plastik/metal/bos, bitirmek için q): q
Sonuçlar test_sonuclari.csv dosyasına kaydedildi.

Her sınıftan en az 10 nesneyle test yapın. Ardından sonuçları değerlendirelim. Kendi test dosyanız henüz yoksa önce örnek bir test dosyası üretebilirsiniz:

# dosya: ornek_test_verisi.py
# Kendi test kaydınız yoksa değerlendirme programını bu örnek veriyle deneyin.
import csv
import random

random.seed(4)
sonuclar = ([("kagit", "kagit")] * 11 + [("kagit", "plastik")] +
            [("plastik", "plastik")] * 9 + [("plastik", "metal")] * 2 + [("plastik", "kagit")] +
            [("metal", "metal")] * 10 + [("metal", "plastik")] * 2 +
            [("bos", "bos")] * 4)
random.shuffle(sonuclar)

with open("test_sonuclari.csv", "w", newline="", encoding="utf-8") as f:
    yazici = csv.writer(f)
    yazici.writerow(["gercek", "tahmin", "guven"])
    for gercek, tahmin in sonuclar:
        guven = random.uniform(0.85, 0.99) if gercek == tahmin else random.uniform(0.6, 0.85)
        yazici.writerow([gercek, tahmin, f"{guven:.2f}"])
print(len(sonuclar), "satırlık örnek test verisi yazıldı.")

Beklenen çıktı:

40 satırlık örnek test verisi yazıldı.
# dosya: degerlendir.py  (test sonuçlarından doğruluk ve karışıklık matrisi)
import csv
import matplotlib.pyplot as plt
from sklearn.metrics import (accuracy_score, confusion_matrix,
                             classification_report, ConfusionMatrixDisplay)

SINIFLAR = ["kagit", "plastik", "metal", "bos"]
gercek, tahmin = [], []
with open("test_sonuclari.csv", encoding="utf-8") as f:
    for satir in csv.DictReader(f):
        gercek.append(satir["gercek"])
        tahmin.append(satir["tahmin"])

print("Test sayısı:", len(gercek))
print(f"Doğruluk: %{accuracy_score(gercek, tahmin) * 100:.1f}")

matris = confusion_matrix(gercek, tahmin, labels=SINIFLAR)
print()
print("Karışıklık matrisi (satır: gerçek, sütun: tahmin)")
print(" " * 9 + "".join(f"{s:>9}" for s in SINIFLAR))
for ad, satir in zip(SINIFLAR, matris):
    print(f"{ad:<9}" + "".join(f"{n:>9}" for n in satir))

print()
print(classification_report(gercek, tahmin, labels=SINIFLAR, zero_division=0))

ConfusionMatrixDisplay(matris, display_labels=SINIFLAR).plot(cmap="Blues", colorbar=False)
plt.title("Karışıklık Matrisi")
plt.xlabel("Tahmin edilen sınıf")
plt.ylabel("Gerçek sınıf")
plt.gcf().set_size_inches(8, 6.5)
plt.tight_layout()
plt.savefig("karisiklik_matrisi.png", dpi=200)
print("karisiklik_matrisi.png kaydedildi.")

Beklenen çıktı:

Test sayısı: 40
Doğruluk: %85.0

Karışıklık matrisi (satır: gerçek, sütun: tahmin)
             kagit  plastik    metal      bos
kagit           11        1        0        0
plastik          1        9        2        0
metal            0        2       10        0
bos              0        0        0        4

              precision    recall  f1-score   support

       kagit       0.92      0.92      0.92        12
     plastik       0.75      0.75      0.75        12
       metal       0.83      0.83      0.83        12
         bos       1.00      1.00      1.00         4

    accuracy                           0.85        40
   macro avg       0.88      0.88      0.88        40
weighted avg       0.85      0.85      0.85        40

karisiklik_matrisi.png kaydedildi.

Bu örnek veride doğruluk %85. Ama asıl ilginç bilgi karışıklık matrisinde. Satırlar gerçek sınıfı, sütunlar tahmini gösterir; köşegendeki sayılar doğru tahminlerdir. “plastik” satırına bakın: 12 plastik atıktan 9’u doğru bilinmiş, 2’si metal, 1’i kâğıt sanılmış. “metal” satırında da 2 metal atık plastik sanılmış. Demek ki modelimiz en çok plastik ile metali karıştırıyor. Parlak, metalik görünümlü plastik ambalajlar bunun nedeni olabilir. Çözüm de belli: Veri setine bu tür zor örnekler eklemek.

Raporun sütunları da İngilizcedir: precision (kesinlik), modelin “metal” dediklerinin ne kadarının gerçekten metal olduğunu; recall (duyarlılık), gerçek metallerin ne kadarını yakalayabildiğini; f1-score ikisinin dengesini; support ise o sınıftaki test sayısını gösterir.

Örnek test verisinin karışıklık matrisi

Proje Değerlendirme Ölçütleri

Ölçüt Puan
Kutu “aç” ve “kapat” komutlarına doğru tepki veriyor; süre dolunca kapanıyor. 20
Atıklar tanınıyor; LED, buzzer ve kayıt dosyası doğru çalışıyor. 20
Test oturumu yapılmış; doğruluk ve karışıklık matrisi hesaplanıp yorumlanmış. 25
Veri seti kurallara uygun hazırlanmış (denge, çeşitlilik, temizlik). 15
Sunum: Sistemin nasıl çalıştığı, sınırlılıkları ve geliştirme fikirleri anlatılmış. 20

⚠️ Önemli Not

Bu kutu bir öğrenme projesidir. Model kimi zaman yanılacaktır; bu yüzden kutunun kararlarına körü körüne güvenmeyin ve kirli, kesici ya da tehlikeli atıkları (cam kırığı, pil, kimyasal kutusu) asla kutuyla denemeyin. Kartonla çalışırken maket bıçağını ve sıcak silikonu öğretmeninizin gözetiminde kullanın; servo ve kablolar takılıyken devreye su ya da sıvı değmemesine dikkat edin.

Sıra Sizde

  1. Kolay: Kendi test oturumunuzu yapın ve degerlendir.py ile sonuçlarınızı hesaplayın. Modeliniz en çok hangi iki sınıfı karıştırıyor?
  2. Orta: kutu_kaydi.csv dosyasını okuyup her atık türünden kaç tane atıldığını ve toplamın yüzde kaçının plastik olduğunu yazan bir program yazın.
  3. Zor: Karışıklık matrisinde en çok hata yapılan sınıflar için veri setine 30’ar yeni “zor” örnek ekleyin, modeli yeniden eğitin ve test oturumunu tekrarlayın. Doğruluk ne kadar değişti? Sonuçları sunumunuza ekleyin.

Kendini Dene

  1. Kutumuzun durum diyagramında hangi durumlar vardır ve AÇIK durumdan KAPALI duruma hangi olaylarla geçilir?
  2. Karışıklık matrisinde “metal” satırı ile “plastik” sütununun kesiştiği hücrede 2 yazıyor. Bu ne anlama gelir?
  3. Test oturumunda neden modelin hiç görmediği nesneleri kullanıyoruz?

Cevaplar

  1. KAPALI ve AÇIK durumları vardır. AÇIK durumdan KAPALI duruma “kapat” komutu geldiğinde ya da 20 saniye boyunca hiçbir atık tanınmadığında geçilir.
  2. Gerçekte metal olan 2 atık, model tarafından plastik olarak tahmin edilmiştir.
  3. Çünkü modelin eğitimde gördüğü nesneleri tanıması kolaydır ve gerçek başarıyı göstermez. Kutu gerçek hayatta hep yeni nesnelerle karşılaşacağı için başarıyı da yeni nesnelerle ölçmeliyiz.