ESP32 Cam Deteksi dan Counter / Hitung Benda / Orang Lewat
Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah esp32 cam bisa berfungsi sebagai penghitung orang yang masuk atau keluar, atau juga bisa menghitung benda yang lewat didepan kamera. untuk detailnya berikut adalah koding dan interfacenya.
1. Interface
2. Program ESP32 Cam
#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"
// ======================================================
// ESP32-CAM AI THINKER PIN
// ======================================================
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
// ======================================================
// HOTSPOT
// ======================================================
const char* AP_SSID = "ESP32CAM_COUNTER";
const char* AP_PASSWORD = "12345678";
WebServer server(80);
// ======================================================
// UKURAN FRAME
// QQVGA = 160 x 120
// ======================================================
#define FRAME_WIDTH 160
#define FRAME_HEIGHT 120
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT)
uint8_t* previousFrame = NULL;
// ======================================================
// ZONA DETEKSI
// ======================================================
// Zona A
#define ZONE_A_X1 30
#define ZONE_A_X2 60
// Zona B
#define ZONE_B_X1 100
#define ZONE_B_X2 130
// Area vertikal jalan
#define ZONE_Y1 20
#define ZONE_Y2 105
// ======================================================
// SENSITIVITAS
// ======================================================
// Perbedaan brightness pixel
#define PIXEL_DIFF_THRESHOLD 25
// Berapa persen pixel harus berubah
#define MOTION_THRESHOLD 12
// Timeout kendaraan mencapai zona berikutnya
#define CROSS_TIMEOUT 3000
// Frame tenang sebelum siap menghitung lagi
#define CLEAR_FRAME_REQUIRED 5
// ======================================================
// COUNTER
// ======================================================
unsigned long vehicleCount = 0;
unsigned long countAToB = 0;
unsigned long countBToA = 0;
String lastDirection = "-";
int motionA = 0;
int motionB = 0;
// ======================================================
// STATE MACHINE
// ======================================================
enum DetectionState {
STATE_IDLE,
STATE_WAIT_B,
STATE_WAIT_A,
STATE_LOCKED
};
DetectionState state = STATE_IDLE;
unsigned long stateStartTime = 0;
int clearFrameCounter = 0;
// ======================================================
// HTML WEBSERVER
// ======================================================
const char PAGE[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1">
<title>ESP32 Vehicle Counter</title>
<style>
body {
background:#111;
color:white;
font-family:Arial;
text-align:center;
margin:0;
padding:20px;
}
.container {
max-width:500px;
margin:auto;
}
.card {
background:#222;
padding:20px;
margin:15px 0;
border-radius:12px;
}
h1 {
font-size:25px;
}
.counter {
font-size:70px;
color:#00ff88;
font-weight:bold;
}
.label {
color:#aaa;
font-size:13px;
}
.value {
font-size:24px;
margin:7px;
}
.zone {
display:flex;
justify-content:space-around;
}
.zonebox {
width:45%;
padding:15px;
background:#333;
border-radius:10px;
}
button {
width:100%;
padding:15px;
background:#d32f2f;
color:white;
border:none;
border-radius:8px;
font-size:16px;
cursor:pointer;
}
.status {
color:#ffb74d;
}
</style>
</head>
<body>
<div class="container">
<h1>
ESP32-CAM Vehicle Counter
</h1>
<div class="card">
<div class="label">
JUMLAH KENDARAAN
</div>
<div
class="counter"
id="count">
0
</div>
</div>
<div class="card">
<div class="zone">
<div class="zonebox">
<div class="label">
A → B
</div>
<div
class="value"
id="ab">
0
</div>
</div>
<div class="zonebox">
<div class="label">
B → A
</div>
<div
class="value"
id="ba">
0
</div>
</div>
</div>
</div>
<div class="card">
<div class="label">
ARAH TERAKHIR
</div>
<div
class="value"
id="direction">
-
</div>
<div class="label">
STATUS SISTEM
</div>
<div
class="value status"
id="state">
MENUNGGU
</div>
</div>
<div class="card">
<div class="zone">
<div class="zonebox">
<div class="label">
MOTION ZONA A
</div>
<div
class="value"
id="motionA">
0%
</div>
</div>
<div class="zonebox">
<div class="label">
MOTION ZONA B
</div>
<div
class="value"
id="motionB">
0%
</div>
</div>
</div>
</div>
<div class="card">
<button onclick="resetCounter()">
RESET COUNTER
</button>
</div>
<div class="label">
WiFi: ESP32CAM_COUNTER
<br>
IP: 192.168.4.1
</div>
</div>
<script>
function updateData() {
fetch("/status")
.then(response => response.json())
.then(data => {
document.getElementById(
"count"
).innerHTML =
data.count;
document.getElementById(
"ab"
).innerHTML =
data.ab;
document.getElementById(
"ba"
).innerHTML =
data.ba;
document.getElementById(
"direction"
).innerHTML =
data.direction;
document.getElementById(
"state"
).innerHTML =
data.state;
document.getElementById(
"motionA"
).innerHTML =
data.motionA + "%";
document.getElementById(
"motionB"
).innerHTML =
data.motionB + "%";
});
}
function resetCounter() {
fetch("/reset");
}
setInterval(
updateData,
500
);
updateData();
</script>
</body>
</html>
)rawliteral";
// ======================================================
// HITUNG PERUBAHAN PIXEL DALAM ZONA
// ======================================================
int calculateMotion(
uint8_t* current,
uint8_t* previous,
int x1,
int x2,
int y1,
int y2
) {
unsigned long changed = 0;
unsigned long total = 0;
// Skip beberapa pixel supaya lebih ringan
const int STEP = 2;
for (
int y = y1;
y < y2;
y += STEP
) {
for (
int x = x1;
x < x2;
x += STEP
) {
int index =
y * FRAME_WIDTH + x;
int difference =
abs(
(int)current[index]
-
(int)previous[index]
);
if (
difference >
PIXEL_DIFF_THRESHOLD
) {
changed++;
}
total++;
}
}
if (
total == 0
) {
return 0;
}
return
(changed * 100)
/
total;
}
// ======================================================
// PROSES COUNTER
// ======================================================
void processVehicle(
bool zoneA,
bool zoneB
) {
unsigned long now =
millis();
switch(state) {
// ==================================================
// MENUNGGU KENDARAAN
// ==================================================
case STATE_IDLE:
if (
zoneA &&
!zoneB
) {
state =
STATE_WAIT_B;
stateStartTime =
now;
Serial.println(
"Zona A aktif -> menunggu Zona B"
);
}
else if (
zoneB &&
!zoneA
) {
state =
STATE_WAIT_A;
stateStartTime =
now;
Serial.println(
"Zona B aktif -> menunggu Zona A"
);
}
break;
// ==================================================
// A -> B
// ==================================================
case STATE_WAIT_B:
if (zoneB) {
vehicleCount++;
countAToB++;
lastDirection =
"A -> B";
Serial.println();
Serial.println(
"========================="
);
Serial.println(
"KENDARAAN TERHITUNG"
);
Serial.print(
"Arah : "
);
Serial.println(
"A -> B"
);
Serial.print(
"Total : "
);
Serial.println(
vehicleCount
);
Serial.println(
"========================="
);
state =
STATE_LOCKED;
clearFrameCounter =
0;
}
else if (
now - stateStartTime >
CROSS_TIMEOUT
) {
state =
STATE_IDLE;
}
break;
// ==================================================
// B -> A
// ==================================================
case STATE_WAIT_A:
if (zoneA) {
vehicleCount++;
countBToA++;
lastDirection =
"B -> A";
Serial.println();
Serial.println(
"========================="
);
Serial.println(
"KENDARAAN TERHITUNG"
);
Serial.print(
"Arah : "
);
Serial.println(
"B -> A"
);
Serial.print(
"Total : "
);
Serial.println(
vehicleCount
);
Serial.println(
"========================="
);
state =
STATE_LOCKED;
clearFrameCounter =
0;
}
else if (
now - stateStartTime >
CROSS_TIMEOUT
) {
state =
STATE_IDLE;
}
break;
// ==================================================
// TUNGGU KENDARAAN KELUAR
// ==================================================
case STATE_LOCKED:
if (
!zoneA &&
!zoneB
) {
clearFrameCounter++;
if (
clearFrameCounter >=
CLEAR_FRAME_REQUIRED
) {
state =
STATE_IDLE;
clearFrameCounter =
0;
Serial.println(
"Area kosong. Siap kendaraan berikutnya."
);
}
}
else {
clearFrameCounter = 0;
}
break;
}
}
// ======================================================
// NAMA STATUS
// ======================================================
String getStateName() {
switch(state) {
case STATE_IDLE:
return "MENUNGGU";
case STATE_WAIT_B:
return "A TERDETEKSI";
case STATE_WAIT_A:
return "B TERDETEKSI";
case STATE_LOCKED:
return "KENDARAAN LEWAT";
}
return "-";
}
// ======================================================
// WEBSERVER
// ======================================================
void setupWebServer() {
server.on(
"/",
HTTP_GET,
[]() {
server.send_P(
200,
"text/html",
PAGE
);
}
);
server.on(
"/status",
HTTP_GET,
[]() {
String json =
"{";
json +=
"\"count\":" +
String(vehicleCount) +
",";
json +=
"\"ab\":" +
String(countAToB) +
",";
json +=
"\"ba\":" +
String(countBToA) +
",";
json +=
"\"direction\":\"" +
lastDirection +
"\",";
json +=
"\"state\":\"" +
getStateName() +
"\",";
json +=
"\"motionA\":" +
String(motionA) +
",";
json +=
"\"motionB\":" +
String(motionB);
json += "}";
server.send(
200,
"application/json",
json
);
}
);
server.on(
"/reset",
HTTP_GET,
[]() {
vehicleCount = 0;
countAToB = 0;
countBToA = 0;
lastDirection = "-";
state =
STATE_IDLE;
server.send(
200,
"text/plain",
"RESET OK"
);
Serial.println(
"Counter di-reset"
);
}
);
server.begin();
Serial.println(
"Webserver aktif"
);
}
// ======================================================
// SETUP
// ======================================================
void setup() {
Serial.begin(
115200
);
Serial.println();
Serial.println(
"=============================="
);
Serial.println(
"ESP32-CAM VEHICLE COUNTER"
);
Serial.println(
"=============================="
);
// ==================================================
// CAMERA CONFIG
// ==================================================
camera_config_t config;
config.ledc_channel =
LEDC_CHANNEL_0;
config.ledc_timer =
LEDC_TIMER_0;
config.pin_d0 =
Y2_GPIO_NUM;
config.pin_d1 =
Y3_GPIO_NUM;
config.pin_d2 =
Y4_GPIO_NUM;
config.pin_d3 =
Y5_GPIO_NUM;
config.pin_d4 =
Y6_GPIO_NUM;
config.pin_d5 =
Y7_GPIO_NUM;
config.pin_d6 =
Y8_GPIO_NUM;
config.pin_d7 =
Y9_GPIO_NUM;
config.pin_xclk =
XCLK_GPIO_NUM;
config.pin_pclk =
PCLK_GPIO_NUM;
config.pin_vsync =
VSYNC_GPIO_NUM;
config.pin_href =
HREF_GPIO_NUM;
config.pin_sscb_sda =
SIOD_GPIO_NUM;
config.pin_sscb_scl =
SIOC_GPIO_NUM;
config.pin_pwdn =
PWDN_GPIO_NUM;
config.pin_reset =
RESET_GPIO_NUM;
config.xclk_freq_hz =
20000000;
// Grayscale jauh lebih ringan
// daripada RGB / JPEG
config.pixel_format =
PIXFORMAT_GRAYSCALE;
config.frame_size =
FRAMESIZE_QQVGA;
config.jpeg_quality =
12;
config.fb_count =
1;
// ==================================================
// START CAMERA
// ==================================================
esp_err_t err =
esp_camera_init(
&config
);
if (
err != ESP_OK
) {
Serial.printf(
"Camera error: 0x%x\n",
err
);
return;
}
Serial.println(
"Camera berhasil"
);
// ==================================================
// ALOKASI FRAME SEBELUMNYA
// ==================================================
if (
psramFound()
) {
previousFrame =
(uint8_t*)
ps_malloc(
FRAME_SIZE
);
}
else {
previousFrame =
(uint8_t*)
malloc(
FRAME_SIZE
);
}
if (
previousFrame == NULL
) {
Serial.println(
"Gagal alokasi previousFrame"
);
return;
}
memset(
previousFrame,
0,
FRAME_SIZE
);
// ==================================================
// ACCESS POINT
// ==================================================
WiFi.mode(
WIFI_AP
);
IPAddress localIP(
192,
168,
4,
1
);
IPAddress gateway(
192,
168,
4,
1
);
IPAddress subnet(
255,
255,
255,
0
);
WiFi.softAPConfig(
localIP,
gateway,
subnet
);
WiFi.softAP(
AP_SSID,
AP_PASSWORD
);
Serial.println();
Serial.println(
"HOTSPOT AKTIF"
);
Serial.print(
"SSID : "
);
Serial.println(
AP_SSID
);
Serial.print(
"Password : "
);
Serial.println(
AP_PASSWORD
);
Serial.print(
"IP : "
);
Serial.println(
WiFi.softAPIP()
);
setupWebServer();
Serial.println();
Serial.println(
"Buka:"
);
Serial.println(
"http://192.168.4.1"
);
Serial.println(
"=============================="
);
}
// ======================================================
// LOOP
// ======================================================
void loop() {
server.handleClient();
camera_fb_t* fb =
esp_camera_fb_get();
if (
fb == NULL
) {
delay(20);
return;
}
// Pastikan grayscale
// 160 x 120
if (
fb->format !=
PIXFORMAT_GRAYSCALE
) {
esp_camera_fb_return(
fb
);
return;
}
// ==================================================
// FRAME PERTAMA
// ==================================================
static bool firstFrame =
true;
if(firstFrame) {
memcpy(
previousFrame,
fb->buf,
FRAME_SIZE
);
firstFrame =
false;
esp_camera_fb_return(
fb
);
delay(100);
return;
}
// ==================================================
// HITUNG MOTION
// ==================================================
motionA =
calculateMotion(
fb->buf,
previousFrame,
ZONE_A_X1,
ZONE_A_X2,
ZONE_Y1,
ZONE_Y2
);
motionB =
calculateMotion(
fb->buf,
previousFrame,
ZONE_B_X1,
ZONE_B_X2,
ZONE_Y1,
ZONE_Y2
);
bool zoneAActive =
motionA >=
MOTION_THRESHOLD;
bool zoneBActive =
motionB >=
MOTION_THRESHOLD;
processVehicle(
zoneAActive,
zoneBActive
);
// ==================================================
// SIMPAN FRAME SEKARANG
// ==================================================
memcpy(
previousFrame,
fb->buf,
FRAME_SIZE
);
esp_camera_fb_return(
fb
);
delay(70);
}
#include <WebServer.h>
#include "esp_camera.h"
// ======================================================
// ESP32-CAM AI THINKER PIN
// ======================================================
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
// ======================================================
// HOTSPOT
// ======================================================
const char* AP_SSID = "ESP32CAM_COUNTER";
const char* AP_PASSWORD = "12345678";
WebServer server(80);
// ======================================================
// UKURAN FRAME
// QQVGA = 160 x 120
// ======================================================
#define FRAME_WIDTH 160
#define FRAME_HEIGHT 120
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT)
uint8_t* previousFrame = NULL;
// ======================================================
// ZONA DETEKSI
// ======================================================
// Zona A
#define ZONE_A_X1 30
#define ZONE_A_X2 60
// Zona B
#define ZONE_B_X1 100
#define ZONE_B_X2 130
// Area vertikal jalan
#define ZONE_Y1 20
#define ZONE_Y2 105
// ======================================================
// SENSITIVITAS
// ======================================================
// Perbedaan brightness pixel
#define PIXEL_DIFF_THRESHOLD 25
// Berapa persen pixel harus berubah
#define MOTION_THRESHOLD 12
// Timeout kendaraan mencapai zona berikutnya
#define CROSS_TIMEOUT 3000
// Frame tenang sebelum siap menghitung lagi
#define CLEAR_FRAME_REQUIRED 5
// ======================================================
// COUNTER
// ======================================================
unsigned long vehicleCount = 0;
unsigned long countAToB = 0;
unsigned long countBToA = 0;
String lastDirection = "-";
int motionA = 0;
int motionB = 0;
// ======================================================
// STATE MACHINE
// ======================================================
enum DetectionState {
STATE_IDLE,
STATE_WAIT_B,
STATE_WAIT_A,
STATE_LOCKED
};
DetectionState state = STATE_IDLE;
unsigned long stateStartTime = 0;
int clearFrameCounter = 0;
// ======================================================
// HTML WEBSERVER
// ======================================================
const char PAGE[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1">
<title>ESP32 Vehicle Counter</title>
<style>
body {
background:#111;
color:white;
font-family:Arial;
text-align:center;
margin:0;
padding:20px;
}
.container {
max-width:500px;
margin:auto;
}
.card {
background:#222;
padding:20px;
margin:15px 0;
border-radius:12px;
}
h1 {
font-size:25px;
}
.counter {
font-size:70px;
color:#00ff88;
font-weight:bold;
}
.label {
color:#aaa;
font-size:13px;
}
.value {
font-size:24px;
margin:7px;
}
.zone {
display:flex;
justify-content:space-around;
}
.zonebox {
width:45%;
padding:15px;
background:#333;
border-radius:10px;
}
button {
width:100%;
padding:15px;
background:#d32f2f;
color:white;
border:none;
border-radius:8px;
font-size:16px;
cursor:pointer;
}
.status {
color:#ffb74d;
}
</style>
</head>
<body>
<div class="container">
<h1>
ESP32-CAM Vehicle Counter
</h1>
<div class="card">
<div class="label">
JUMLAH KENDARAAN
</div>
<div
class="counter"
id="count">
0
</div>
</div>
<div class="card">
<div class="zone">
<div class="zonebox">
<div class="label">
A → B
</div>
<div
class="value"
id="ab">
0
</div>
</div>
<div class="zonebox">
<div class="label">
B → A
</div>
<div
class="value"
id="ba">
0
</div>
</div>
</div>
</div>
<div class="card">
<div class="label">
ARAH TERAKHIR
</div>
<div
class="value"
id="direction">
-
</div>
<div class="label">
STATUS SISTEM
</div>
<div
class="value status"
id="state">
MENUNGGU
</div>
</div>
<div class="card">
<div class="zone">
<div class="zonebox">
<div class="label">
MOTION ZONA A
</div>
<div
class="value"
id="motionA">
0%
</div>
</div>
<div class="zonebox">
<div class="label">
MOTION ZONA B
</div>
<div
class="value"
id="motionB">
0%
</div>
</div>
</div>
</div>
<div class="card">
<button onclick="resetCounter()">
RESET COUNTER
</button>
</div>
<div class="label">
WiFi: ESP32CAM_COUNTER
<br>
IP: 192.168.4.1
</div>
</div>
<script>
function updateData() {
fetch("/status")
.then(response => response.json())
.then(data => {
document.getElementById(
"count"
).innerHTML =
data.count;
document.getElementById(
"ab"
).innerHTML =
data.ab;
document.getElementById(
"ba"
).innerHTML =
data.ba;
document.getElementById(
"direction"
).innerHTML =
data.direction;
document.getElementById(
"state"
).innerHTML =
data.state;
document.getElementById(
"motionA"
).innerHTML =
data.motionA + "%";
document.getElementById(
"motionB"
).innerHTML =
data.motionB + "%";
});
}
function resetCounter() {
fetch("/reset");
}
setInterval(
updateData,
500
);
updateData();
</script>
</body>
</html>
)rawliteral";
// ======================================================
// HITUNG PERUBAHAN PIXEL DALAM ZONA
// ======================================================
int calculateMotion(
uint8_t* current,
uint8_t* previous,
int x1,
int x2,
int y1,
int y2
) {
unsigned long changed = 0;
unsigned long total = 0;
// Skip beberapa pixel supaya lebih ringan
const int STEP = 2;
for (
int y = y1;
y < y2;
y += STEP
) {
for (
int x = x1;
x < x2;
x += STEP
) {
int index =
y * FRAME_WIDTH + x;
int difference =
abs(
(int)current[index]
-
(int)previous[index]
);
if (
difference >
PIXEL_DIFF_THRESHOLD
) {
changed++;
}
total++;
}
}
if (
total == 0
) {
return 0;
}
return
(changed * 100)
/
total;
}
// ======================================================
// PROSES COUNTER
// ======================================================
void processVehicle(
bool zoneA,
bool zoneB
) {
unsigned long now =
millis();
switch(state) {
// ==================================================
// MENUNGGU KENDARAAN
// ==================================================
case STATE_IDLE:
if (
zoneA &&
!zoneB
) {
state =
STATE_WAIT_B;
stateStartTime =
now;
Serial.println(
"Zona A aktif -> menunggu Zona B"
);
}
else if (
zoneB &&
!zoneA
) {
state =
STATE_WAIT_A;
stateStartTime =
now;
Serial.println(
"Zona B aktif -> menunggu Zona A"
);
}
break;
// ==================================================
// A -> B
// ==================================================
case STATE_WAIT_B:
if (zoneB) {
vehicleCount++;
countAToB++;
lastDirection =
"A -> B";
Serial.println();
Serial.println(
"========================="
);
Serial.println(
"KENDARAAN TERHITUNG"
);
Serial.print(
"Arah : "
);
Serial.println(
"A -> B"
);
Serial.print(
"Total : "
);
Serial.println(
vehicleCount
);
Serial.println(
"========================="
);
state =
STATE_LOCKED;
clearFrameCounter =
0;
}
else if (
now - stateStartTime >
CROSS_TIMEOUT
) {
state =
STATE_IDLE;
}
break;
// ==================================================
// B -> A
// ==================================================
case STATE_WAIT_A:
if (zoneA) {
vehicleCount++;
countBToA++;
lastDirection =
"B -> A";
Serial.println();
Serial.println(
"========================="
);
Serial.println(
"KENDARAAN TERHITUNG"
);
Serial.print(
"Arah : "
);
Serial.println(
"B -> A"
);
Serial.print(
"Total : "
);
Serial.println(
vehicleCount
);
Serial.println(
"========================="
);
state =
STATE_LOCKED;
clearFrameCounter =
0;
}
else if (
now - stateStartTime >
CROSS_TIMEOUT
) {
state =
STATE_IDLE;
}
break;
// ==================================================
// TUNGGU KENDARAAN KELUAR
// ==================================================
case STATE_LOCKED:
if (
!zoneA &&
!zoneB
) {
clearFrameCounter++;
if (
clearFrameCounter >=
CLEAR_FRAME_REQUIRED
) {
state =
STATE_IDLE;
clearFrameCounter =
0;
Serial.println(
"Area kosong. Siap kendaraan berikutnya."
);
}
}
else {
clearFrameCounter = 0;
}
break;
}
}
// ======================================================
// NAMA STATUS
// ======================================================
String getStateName() {
switch(state) {
case STATE_IDLE:
return "MENUNGGU";
case STATE_WAIT_B:
return "A TERDETEKSI";
case STATE_WAIT_A:
return "B TERDETEKSI";
case STATE_LOCKED:
return "KENDARAAN LEWAT";
}
return "-";
}
// ======================================================
// WEBSERVER
// ======================================================
void setupWebServer() {
server.on(
"/",
HTTP_GET,
[]() {
server.send_P(
200,
"text/html",
PAGE
);
}
);
server.on(
"/status",
HTTP_GET,
[]() {
String json =
"{";
json +=
"\"count\":" +
String(vehicleCount) +
",";
json +=
"\"ab\":" +
String(countAToB) +
",";
json +=
"\"ba\":" +
String(countBToA) +
",";
json +=
"\"direction\":\"" +
lastDirection +
"\",";
json +=
"\"state\":\"" +
getStateName() +
"\",";
json +=
"\"motionA\":" +
String(motionA) +
",";
json +=
"\"motionB\":" +
String(motionB);
json += "}";
server.send(
200,
"application/json",
json
);
}
);
server.on(
"/reset",
HTTP_GET,
[]() {
vehicleCount = 0;
countAToB = 0;
countBToA = 0;
lastDirection = "-";
state =
STATE_IDLE;
server.send(
200,
"text/plain",
"RESET OK"
);
Serial.println(
"Counter di-reset"
);
}
);
server.begin();
Serial.println(
"Webserver aktif"
);
}
// ======================================================
// SETUP
// ======================================================
void setup() {
Serial.begin(
115200
);
Serial.println();
Serial.println(
"=============================="
);
Serial.println(
"ESP32-CAM VEHICLE COUNTER"
);
Serial.println(
"=============================="
);
// ==================================================
// CAMERA CONFIG
// ==================================================
camera_config_t config;
config.ledc_channel =
LEDC_CHANNEL_0;
config.ledc_timer =
LEDC_TIMER_0;
config.pin_d0 =
Y2_GPIO_NUM;
config.pin_d1 =
Y3_GPIO_NUM;
config.pin_d2 =
Y4_GPIO_NUM;
config.pin_d3 =
Y5_GPIO_NUM;
config.pin_d4 =
Y6_GPIO_NUM;
config.pin_d5 =
Y7_GPIO_NUM;
config.pin_d6 =
Y8_GPIO_NUM;
config.pin_d7 =
Y9_GPIO_NUM;
config.pin_xclk =
XCLK_GPIO_NUM;
config.pin_pclk =
PCLK_GPIO_NUM;
config.pin_vsync =
VSYNC_GPIO_NUM;
config.pin_href =
HREF_GPIO_NUM;
config.pin_sscb_sda =
SIOD_GPIO_NUM;
config.pin_sscb_scl =
SIOC_GPIO_NUM;
config.pin_pwdn =
PWDN_GPIO_NUM;
config.pin_reset =
RESET_GPIO_NUM;
config.xclk_freq_hz =
20000000;
// Grayscale jauh lebih ringan
// daripada RGB / JPEG
config.pixel_format =
PIXFORMAT_GRAYSCALE;
config.frame_size =
FRAMESIZE_QQVGA;
config.jpeg_quality =
12;
config.fb_count =
1;
// ==================================================
// START CAMERA
// ==================================================
esp_err_t err =
esp_camera_init(
&config
);
if (
err != ESP_OK
) {
Serial.printf(
"Camera error: 0x%x\n",
err
);
return;
}
Serial.println(
"Camera berhasil"
);
// ==================================================
// ALOKASI FRAME SEBELUMNYA
// ==================================================
if (
psramFound()
) {
previousFrame =
(uint8_t*)
ps_malloc(
FRAME_SIZE
);
}
else {
previousFrame =
(uint8_t*)
malloc(
FRAME_SIZE
);
}
if (
previousFrame == NULL
) {
Serial.println(
"Gagal alokasi previousFrame"
);
return;
}
memset(
previousFrame,
0,
FRAME_SIZE
);
// ==================================================
// ACCESS POINT
// ==================================================
WiFi.mode(
WIFI_AP
);
IPAddress localIP(
192,
168,
4,
1
);
IPAddress gateway(
192,
168,
4,
1
);
IPAddress subnet(
255,
255,
255,
0
);
WiFi.softAPConfig(
localIP,
gateway,
subnet
);
WiFi.softAP(
AP_SSID,
AP_PASSWORD
);
Serial.println();
Serial.println(
"HOTSPOT AKTIF"
);
Serial.print(
"SSID : "
);
Serial.println(
AP_SSID
);
Serial.print(
"Password : "
);
Serial.println(
AP_PASSWORD
);
Serial.print(
"IP : "
);
Serial.println(
WiFi.softAPIP()
);
setupWebServer();
Serial.println();
Serial.println(
"Buka:"
);
Serial.println(
"http://192.168.4.1"
);
Serial.println(
"=============================="
);
}
// ======================================================
// LOOP
// ======================================================
void loop() {
server.handleClient();
camera_fb_t* fb =
esp_camera_fb_get();
if (
fb == NULL
) {
delay(20);
return;
}
// Pastikan grayscale
// 160 x 120
if (
fb->format !=
PIXFORMAT_GRAYSCALE
) {
esp_camera_fb_return(
fb
);
return;
}
// ==================================================
// FRAME PERTAMA
// ==================================================
static bool firstFrame =
true;
if(firstFrame) {
memcpy(
previousFrame,
fb->buf,
FRAME_SIZE
);
firstFrame =
false;
esp_camera_fb_return(
fb
);
delay(100);
return;
}
// ==================================================
// HITUNG MOTION
// ==================================================
motionA =
calculateMotion(
fb->buf,
previousFrame,
ZONE_A_X1,
ZONE_A_X2,
ZONE_Y1,
ZONE_Y2
);
motionB =
calculateMotion(
fb->buf,
previousFrame,
ZONE_B_X1,
ZONE_B_X2,
ZONE_Y1,
ZONE_Y2
);
bool zoneAActive =
motionA >=
MOTION_THRESHOLD;
bool zoneBActive =
motionB >=
MOTION_THRESHOLD;
processVehicle(
zoneAActive,
zoneBActive
);
// ==================================================
// SIMPAN FRAME SEKARANG
// ==================================================
memcpy(
previousFrame,
fb->buf,
FRAME_SIZE
);
esp_camera_fb_return(
fb
);
delay(70);
}
3. VIDEO HASILNYA


No comments:
Post a Comment