Membuat Server Computer Vision Di Laptop + Endpoint Untuk Menerima Foto dari ESP32-CAM

Siap. Kita mulai CV-01: membuat server Computer Vision di laptop yang menerima foto dari ESP32-CAM melalui Wi-Fi.

Untuk tahap ini belum memakai YOLO dan belum memakai database. Kita pastikan dulu jalur paling dasar berhasil:

ESP32-CAM
   │
   │ HTTP POST /api/v1/vision/upload
   │ JPEG
   ▼
Laptop
   │
   ▼
FastAPI
   │
   ├── validasi gambar
   ├── simpan JPEG
   └── kembalikan JSON

Setelah ini berhasil, kita sambungkan OpenCV.


---

1. Software di laptop

Kita gunakan:

Python

FastAPI

Uvicorn

Pillow


Kalau environment XiaoZhi Anda sebelumnya sudah menggunakan conda, saya sarankan buat environment khusus supaya tidak mengganggu XiaoZhi.

Di Anaconda Prompt:

conda create -n vision python=3.10 -y
conda activate vision

Kemudian:

pip install fastapi uvicorn python-multipart pillow

Cek:

python --version

Harus menunjukkan Python 3.10.x.


---

2. Buat folder project

Misalnya:

C:\smart-incubator\

Buat:

C:\smart-incubator\
├── server\
│ └── main.py
└── storage\
    └── images\

Jadi nanti:

C:\smart-incubator\storage\images

akan menjadi tempat foto dari ESP32-CAM.


---

3. Buat main.py

Buat:

C:\smart-incubator\server\main.py

Isi seluruhnya dengan kode berikut:

from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from PIL import Image
from datetime import datetime
from pathlib import Path
import io
import uuid


# ============================================================
# SMART INCUBATOR - COMPUTER VISION SERVER V1
# CV-01
# ============================================================

APP_NAME = "Smart Incubator Computer Vision Server"
VERSION = "1.0.0"


# ------------------------------------------------------------
# PATH
# ------------------------------------------------------------

BASE_DIR = Path(__file__).resolve().parent.parent

IMAGE_DIR = BASE_DIR / "storage" / "images"

IMAGE_DIR.mkdir(parents=True, exist_ok=True)


# ------------------------------------------------------------
# FASTAPI
# ------------------------------------------------------------

app = FastAPI(
    title=APP_NAME,
    version=VERSION,
    description="Server penerima gambar dari ESP32-CAM"
)


# ------------------------------------------------------------
# ROOT
# ------------------------------------------------------------

@app.get("/")
def root():
    return {
        "system": APP_NAME,
        "version": VERSION,
        "status": "online"
    }


# ------------------------------------------------------------
# HEALTH CHECK
# ------------------------------------------------------------

@app.get("/health")
def health():
    return {
        "status": "ok",
        "service": "computer-vision",
        "version": VERSION
    }


# ------------------------------------------------------------
# UPLOAD IMAGE
# ------------------------------------------------------------

@app.post("/api/v1/vision/upload")
async def upload_image(
    file: UploadFile = File(...)
):

    # --------------------------------------------------------
    # CHECK CONTENT TYPE
    # --------------------------------------------------------

    allowed_types = [
        "image/jpeg",
        "image/jpg",
        "image/png"
    ]

    if file.content_type not in allowed_types:
        raise HTTPException(
            status_code=400,
            detail=f"Format tidak didukung: {file.content_type}"
        )


    # --------------------------------------------------------
    # READ FILE
    # --------------------------------------------------------

    data = await file.read()


    if not data:
        raise HTTPException(
            status_code=400,
            detail="File gambar kosong"
        )


    # --------------------------------------------------------
    # VALIDATE IMAGE
    # --------------------------------------------------------

    try:

        image = Image.open(io.BytesIO(data))

        image.verify()

    except Exception:

        raise HTTPException(
            status_code=400,
            detail="File bukan gambar yang valid"
        )


    # --------------------------------------------------------
    # REOPEN IMAGE
    # --------------------------------------------------------

    image = Image.open(io.BytesIO(data))


    # --------------------------------------------------------
    # IMAGE INFORMATION
    # --------------------------------------------------------

    width, height = image.size


    # --------------------------------------------------------
    # CREATE FILENAME
    # --------------------------------------------------------

    now = datetime.now()

    date_folder = IMAGE_DIR / now.strftime("%Y-%m-%d")

    date_folder.mkdir(
        parents=True,
        exist_ok=True
    )


    timestamp = now.strftime(
        "%Y%m%d_%H%M%S_%f"
    )[:-3]

    unique_id = uuid.uuid4().hex[:8]

    filename = (
        f"egg_{timestamp}_{unique_id}.jpg"
    )


    filepath = date_folder / filename


    # --------------------------------------------------------
    # SAVE AS JPEG
    # --------------------------------------------------------

    if image.mode != "RGB":

        image = image.convert("RGB")


    image.save(
        filepath,
        format="JPEG",
        quality=95
    )


    # --------------------------------------------------------
    # RESPONSE
    # --------------------------------------------------------

    return JSONResponse(
        content={
            "status": "success",
            "message": "Image received",
            "filename": filename,
            "path": str(filepath.relative_to(BASE_DIR)),
            "width": width,
            "height": height,
            "size_bytes": len(data),
            "timestamp": now.isoformat()
        }
    )


# ------------------------------------------------------------
# SERVER START MESSAGE
# ------------------------------------------------------------

if __name__ == "__main__":

    import uvicorn

    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        reload=False
    )


---

4. Jalankan server

Masuk ke:

cd C:\smart-incubator\server

Pastikan environment:

conda activate vision

Kemudian:

python main.py

Kalau berhasil, kira-kira:

INFO: Started server process
INFO: Uvicorn running on http://0.0.0.0:8000

Jangan tutup jendela ini.

Itulah Computer Vision Server kita.


---

5. Tes dari laptop terlebih dahulu

Buka browser laptop.

Masukkan:

http://127.0.0.1:8000

Harus muncul:

{
  "system": "Smart Incubator Computer Vision Server",
  "version": "1.0.0",
  "status": "online"
}

Kemudian:

http://127.0.0.1:8000/health

Hasil:

{
  "status": "ok",
  "service": "computer-vision",
  "version": "1.0.0"
}

Kalau ini berhasil berarti server hidup.


---

6. Kita punya endpoint khusus ESP32-CAM

Endpoint kita:

POST
/api/v1/vision/upload

Alamat lengkap di jaringan lokal nanti:

http://IP-LAPTOP:8000/api/v1/vision/upload

Misalnya IP laptop:

192.168.1.10

maka:

http://192.168.1.10:8000/api/v1/vision/upload

Jangan gunakan 127.0.0.1 dari ESP32.

Ini penting.

127.0.0.1 berarti perangkat itu sendiri.

Jadi:

Laptop:
127.0.0.1 → laptop

ESP32:
127.0.0.1 → ESP32

ESP32 harus menggunakan IP laptop.


---

7. Cara mengetahui IP laptop

Di Windows:

ipconfig

Cari:

Wireless LAN adapter Wi-Fi

Kemudian:

IPv4 Address

Misalnya:

192.168.1.15

Berarti ESP32 nantinya mengirim ke:

http://192.168.1.15:8000/api/v1/vision/upload


---

8. Tes endpoint menggunakan foto

Sebelum ESP32 kita libatkan, saya sangat menyarankan kita melakukan tes dari laptop.

Misalnya ada:

C:\test\egg.jpg

Di PowerShell:

curl.exe -X POST "http://127.0.0.1:8000/api/v1/vision/upload" -F "file=@C:\test\egg.jpg"

Jika berhasil, server mengembalikan sesuatu seperti:

{
    "status": "success",
    "message": "Image received",
    "filename": "egg_20260905_224501_123_a1b2c3d4.jpg",
    "path": "storage\\images\\2026-09-05\\egg_20260905_224501_123_a1b2c3d4.jpg",
    "width": 640,
    "height": 480,
    "size_bytes": 52341,
    "timestamp": "2026-09-05T22:45:01.123456"
}


---

9. Foto akan tersimpan otomatis

Strukturnya menjadi:

C:\smart-incubator\
├── server\
│ └── main.py
└── storage\
    └── images\
        └── 2026-09-05\
            └── egg_20260905_224501_123_a1b2c3d4.jpg

Ini sengaja kita buat berdasarkan tanggal.

Nanti ketika sistem sudah berjalan berhari-hari:

images/
├── 2026-09-05/
├── 2026-09-06/
├── 2026-09-07/
├── 2026-09-08/
└── ...

Ini akan sangat berguna untuk histori Computer Vision dan candling.


---

10. Endpoint ini nantinya bukan hanya menerima foto

Saya sengaja membuat fondasi API seperti ini:

/api/v1/
       │
       └── vision/
             │
             ├── upload
             ├── count
             ├── candling
             └── analyze

Nanti kita bisa kembangkan menjadi:

POST /api/v1/vision/upload

ESP32 mengirim gambar.

Kemudian:

POST /api/v1/vision/count

untuk meminta penghitung telur.

Dan:

POST /api/v1/vision/candling

untuk analisis candling.


---

11. Arsitektur CV kita mulai terlihat

Sekarang:

ESP32-CAM
                 │
              OV5640
                 │
              JPEG
                 │
               Wi-Fi
                 │
                 ▼
       ┌──────────────────┐
       │ FASTAPI SERVER │
       │ │
       │ /vision/upload │
       └────────┬─────────┘
                │
                ▼
          storage/images

Berikutnya kita tambahkan:

                 ▼
              OpenCV
                 │
        ┌────────┴────────┐
        │ │
   preprocessing analysis
        │
        ▼
       YOLO
        │
        ▼
    EGG COUNT


---

12. Kenapa saya ingin OpenCV dulu sebelum YOLO?

Karena kita perlu mengetahui gambar asli dari OV5640.

Sebelum kita menyalahkan model AI, kita harus memastikan:

fokus kamera benar

exposure benar

white balance benar

resolusi benar

pencahayaan cukup

telur tidak blur

posisi kamera benar

gambar tidak terlalu gelap

gambar tidak overexposure


Kalau gambar buruk:

Kamera buruk
    ↓
YOLO bagus
    ↓
hasil tetap buruk

Jadi kita akan melakukan:

CV-01 → transfer gambar

CV-02 → OpenCV image viewer

CV-03 → preprocessing

CV-04 → Egg Counter

CV-05 → YOLO


---

Satu hal penting sebelum ESP32 kita sambungkan

Jangan membuka port 8000 ke internet.

Untuk tahap ini server cukup:

ESP32-CAM
    │
    │ Wi-Fi lokal
    ▼
Laptop

Tidak perlu port forwarding router.

Dan Windows Firewall mungkin akan meminta izin ketika pertama kali menjalankan server. Izinkan Private networks jika laptop dan ESP32 berada pada jaringan Wi-Fi lokal yang sama.


---

Target CV-01 kita

Kita belum perlu menyentuh XiaoZhi, database, atau AI.

Kita harus mencapai kondisi ini terlebih dahulu:

OV5640
  ↓
ESP32-CAM
  ↓
Wi-Fi
  ↓
Laptop
  ↓
FastAPI
  ↓
/api/v1/vision/upload
  ↓
JPEG tersimpan

Setelah itu baru kita buat CV-02: server membaca foto tersebut dengan OpenCV dan menampilkan gambar + informasi resolusi, brightness, contrast, dan histogram. Itu akan menjadi dasar untuk menentukan setting OV5640 yang optimal sebelum kita memasukkan YOLO.

Post a Comment for "Membuat Server Computer Vision Di Laptop + Endpoint Untuk Menerima Foto dari ESP32-CAM"