539 lines
21 KiB
PHP
539 lines
21 KiB
PHP
@extends('landing.layout.main')
|
|
|
|
@section('content')
|
|
<div class="breadcumb-wrapper " data-bg-src="{{ asset('assets/img/breadcumb/breadcumb-bg.jpg') }}">
|
|
<div class="container z-index-common">
|
|
<div class="breadcumb-content">
|
|
<h1 class="breadcumb-title">Pengenalan Hewan</h1>
|
|
<p class="breadcumb-text">Menggunakan Metode CNN (Convolutional Neuro Network)</p>
|
|
<div class="breadcumb-menu-wrap">
|
|
<ul class="breadcumb-menu">
|
|
<li><a href="/">Home</a></li>
|
|
<li>Pengenalan Hewan</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<section class="space">
|
|
<div class="container">
|
|
|
|
<h2 class="pt-3">Materi Pembelajaran Hewan</h2>
|
|
<div class="title-divider1"></div>
|
|
<div class="row text-center justify-content-center">
|
|
@foreach ($kategori as $item)
|
|
<div class="col-lg-3 mb-2">
|
|
<button class="vs-btn form btn-kategori" data-id="{{ $item->id }}">{{ $item->name }}</button>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
|
|
<div class="title-area text-center">
|
|
|
|
</div>
|
|
<div id="hewanCarouselContainer">
|
|
|
|
|
|
</div>
|
|
|
|
<h2 class="pt-3">Pembelajaran Klasifikasi Hewan</h2>
|
|
<div class="title-divider1"></div>
|
|
<div class="row">
|
|
<div class="col-lg-6">
|
|
<div class="list-style1">
|
|
<ul class="list-unstyled mb-0">
|
|
<li>Mengenali hewan menggunakan Convolutional Neural Networks (CNN)</li>
|
|
<li>Klasifikasi secara real-time menggunakan kamera</li>
|
|
<li>Mengunggah gambar untuk identifikasi hewan</li>
|
|
<li>Laporan hasil klasifikasi yang detail</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-6">
|
|
<div class="list-style1">
|
|
<ul class="list-unstyled">
|
|
<li>Interaksi belajar dengan umpan balik langsung</li>
|
|
<li>Identifikasi spesies hewan dengan akurasi tinggi</li>
|
|
<li>Antarmuka yang mudah digunakan untuk anak-anak</li>
|
|
<li>Dukungan pendidikan untuk usia dini</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<h2 class="pt-3">Cara Kerja</h2>
|
|
<div class="title-divider1"></div>
|
|
<svg class="svg-hidden">
|
|
<clipPath id="service-clip1" clipPathUnits="objectBoundingBox">
|
|
<path d="M0.379,0.037 C0.459,-0.006,0.558,-0.006,0.638,0.037 L0.879,0.167 C0.959,0.21,1,0.289,1,0.375 V0.635 C1,0.721,0.959,0.8,0.879,0.843 L0.638,0.973 C0.558,1,0.459,1,0.379,0.973 L0.138,0.843 C0.058,0.8,0.008,0.721,0.008,0.635 V0.375 C0.008,0.289,0.058,0.21,0.138,0.167 L0.379,0.037">
|
|
</path>
|
|
</clipPath>
|
|
</svg>
|
|
<div class="row mb-3 pb-1 justify-content-center">
|
|
<div class="col-md-6 col-lg-4">
|
|
<div class="service-style2">
|
|
<div class="service-icon">
|
|
<div class="service-shape1"></div>
|
|
<div class="service-shape2"></div>
|
|
<div class="service-shape3"></div>
|
|
<img src="{{ asset('assets/img/icon/sr-2-1.svg') }}" alt="icon">
|
|
</div>
|
|
<div class="service-content">
|
|
<h3 class="service-title"><a class="text-inherit" href="class-details.html">Unggah Gambar</a>
|
|
</h3>
|
|
<p class="service-text">Pilih gambar hewan dari perangkat Anda, unggah, dan dapatkan hasil
|
|
klasifikasi secara instan.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-lg-4">
|
|
<div class="service-style2">
|
|
<div class="service-icon">
|
|
<div class="service-shape1"></div>
|
|
<div class="service-shape2"></div>
|
|
<div class="service-shape3"></div>
|
|
<img src="{{ asset('assets/img/icon/sr-2-3.svg') }}" alt="icon">
|
|
</div>
|
|
<div class="service-content">
|
|
<h3 class="service-title"><a class="text-inherit" href="class-details.html">Gunakan Kamera</a>
|
|
</h3>
|
|
<p class="service-text">Gunakan kamera perangkat Anda untuk mendeteksi dan mengklasifikasikan
|
|
hewan secara real-time.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
{{-- <div class="img-box4">
|
|
<div class="img-1 mega-hover">
|
|
<img src="{{ asset('assets/img/about/ab-3-1.jpg') }}" alt="about">
|
|
<a href="https://www.youtube.com/watch?v=_sI_Ps7JSEk" class="play-btn popup-video position-center"><i class="fas fa-play"></i></a>
|
|
</div>
|
|
</div> --}}
|
|
|
|
<!-- form-style4 -->
|
|
<section class="form-section4 space">
|
|
<div class="container-style4">
|
|
<!-- Menu Buttons -->
|
|
<div class="menu-buttons text-center mb-4">
|
|
<button class="vs-btn form" onclick="showSection('upload-classification')">Klasifikasi Unggah
|
|
Gambar</button>
|
|
<button class="vs-btn form" onclick="showSection('live-classification')">Live</button>
|
|
</div>
|
|
|
|
<!-- Upload Classification Form -->
|
|
<div class="form-box4 section-content" id="upload-classification">
|
|
<form id="predictForm" class="form-style4" enctype="multipart/form-data">
|
|
@csrf
|
|
@method('POST')
|
|
<div class="title-area-four form text-center">
|
|
<h2>Klasifikasi Hewan Melalui Gambar</h2>
|
|
<span class="sub-title">Unggah gambar untuk klasifikasi hewan</span>
|
|
</div>
|
|
<div class="row justify-content-between">
|
|
<div class="col-md-12 form-group text-center">
|
|
<button class="vs-btn form" onclick="document.getElementById('fileInput').click();" type="button">Pilih Gambar</button>
|
|
<input type="file" id="fileInput" name="image" style="display: none;">
|
|
</div>
|
|
</div>
|
|
<div id="result2" class="text-center"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Live Classification Form -->
|
|
<div class="form-box4 section-content" id="live-classification" style="display: none;">
|
|
<form class="form-style4">
|
|
<div class="title-area-four form text-center">
|
|
<h2>Klasifikasi Hewan Melalui Live Camera</h2>
|
|
<span class="sub-title">Gunakan kamera untuk klasifikasi hewan secara langsung</span>
|
|
</div>
|
|
<div class="row justify-content-between">
|
|
<div class="col-md-12 form-group text-center">
|
|
<label>Toggle Camera</label>
|
|
<button class="vs-btn form" id="toggleButton" type="button">Mulai Live
|
|
Klasifikasi</button>
|
|
</div>
|
|
<div class="col-md-12 form-group text-center">
|
|
<label>Pilih Kamera</label>
|
|
<select id="cameraSelect"></select>
|
|
</div>
|
|
<div class="col-md-12 form-group text-center">
|
|
<video id="video" width="100%" height="400px" autoplay></video>
|
|
<canvas id="canvas" width="640" height="480" style="display: none;"></canvas>
|
|
</div>
|
|
<div id="result" class="text-center"></div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- End form-style4 -->
|
|
</div>
|
|
</section>
|
|
@endsection
|
|
|
|
@section('css')
|
|
<style>
|
|
.menu-buttons .vs-btn {
|
|
margin: 5px;
|
|
}
|
|
|
|
.section-content {
|
|
display: none;
|
|
}
|
|
|
|
#upload-classification {
|
|
display: block;
|
|
}
|
|
|
|
video {
|
|
border-radius: 15px;
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
/* border: 2px solid #ddd; */
|
|
}
|
|
|
|
</style>
|
|
@endsection
|
|
|
|
|
|
@section('script')
|
|
|
|
|
|
<script>
|
|
let currentUtterance = null;
|
|
let isSpeaking = false;
|
|
|
|
function toggleSpeech(button, elementId) {
|
|
const text = document.getElementById(elementId).innerText;
|
|
|
|
if (isSpeaking) {
|
|
// Jika sedang bicara, stop
|
|
window.speechSynthesis.cancel();
|
|
isSpeaking = false;
|
|
button.innerText = '🔊 Play';
|
|
} else {
|
|
// Jika tidak sedang bicara, mulai dari awal
|
|
window.speechSynthesis.cancel(); // pastikan tidak tumpang tindih
|
|
currentUtterance = new SpeechSynthesisUtterance(text);
|
|
currentUtterance.lang = 'id-ID';
|
|
|
|
currentUtterance.onend = () => {
|
|
isSpeaking = false;
|
|
button.innerText = '🔊 Play';
|
|
};
|
|
|
|
setTimeout(() => {
|
|
window.speechSynthesis.speak(currentUtterance);
|
|
}, 100); // delay 100ms
|
|
isSpeaking = true;
|
|
button.innerText = '⏹️ Stop';
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
|
|
// Close carousel
|
|
$(document).on('click', '.btn-close-carousel', function() {
|
|
$('#hewanCarouselContainer').slideUp(function() {
|
|
$(this).html(''); // Bersihkan kontainer setelah slide up selesai
|
|
});
|
|
});
|
|
|
|
// Ketika tombol kategori diklik
|
|
$('.btn-kategori').on('click', function() {
|
|
var kategoriId = $(this).data('id');
|
|
|
|
$.ajax({
|
|
url: '/getHewanByKategori/' + kategoriId
|
|
, method: 'GET'
|
|
, success: function(data) {
|
|
|
|
function escapeHTML(text) {
|
|
return text.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
|
|
if (data.length > 0) {
|
|
|
|
|
|
var carouselHtml = `
|
|
<div class="vs-carousel mb-2" data-fade="true" data-dots="true" data-xl-dots="true" data-ml-dots="true"
|
|
data-lg-dots="true" data-md-dots="true" data-sm-dots="true" data-xs-dots="true">
|
|
`;
|
|
|
|
// Loop data hewan dan buat item carousel
|
|
$.each(data, function(index, hewan) {
|
|
const descId = `desc-${Math.random().toString(36).substring(2, 8)}`; // Generate unique ID
|
|
carouselHtml += `
|
|
<div>
|
|
<div class="testi-style1">
|
|
<div class="testi-icon"><i class="fas fa-quote-left"></i></div>
|
|
<h3 class="testi-name h2">${hewan.name}</h3>
|
|
<img src="${hewan.foto}" alt="${hewan.name}" class="d-block mx-auto mb-2"
|
|
style="max-height: 200px; object-fit: cover; border-radius: 10px;">
|
|
<p class="testi-text" id="${descId}">${escapeHTML(hewan.description)}</p>
|
|
<br>
|
|
<div class="text-center">
|
|
<button type="button" onclick="toggleSpeech(this, '${descId}')" class="btn vs-btn">🔊 Play</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
carouselHtml += `</div>
|
|
<!-- Tombol Close Tengah -->
|
|
<div class="text-center mt-2">
|
|
<button class="vs-btn form btn-kategori btn-close-carousel">Close</button>
|
|
</div>
|
|
`;
|
|
|
|
// Tampilkan carousel di container
|
|
$('#hewanCarouselContainer').hide().html(carouselHtml).slideDown(); // Tambah hide() biar slideDown jalan
|
|
|
|
// Re-initialize carousel (kalau pakai plugin)
|
|
$('.vs-carousel').slick({
|
|
infinite: false
|
|
, slidesToShow: 1
|
|
, slidesToScroll: 1
|
|
, dots: true
|
|
, arrows: false
|
|
});
|
|
} else {
|
|
$('#hewanCarouselContainer').hide().html(
|
|
'<div class="alert alert-warning text-center">Tidak ada hewan untuk kategori ini.</div>'
|
|
).slideDown();
|
|
}
|
|
}
|
|
, error: function() {
|
|
$('#hewanCarouselContainer').hide().html(
|
|
'<div class="alert alert-danger text-center">Gagal memuat data hewan.</div>'
|
|
).slideDown();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
</script>
|
|
|
|
<script>
|
|
function showSection(sectionId) {
|
|
var sections = document.querySelectorAll('.section-content');
|
|
sections.forEach(function(section) {
|
|
section.style.display = 'none';
|
|
});
|
|
document.getElementById(sectionId).style.display = 'block';
|
|
}
|
|
|
|
</script>
|
|
|
|
<script>
|
|
const videoElement = document.getElementById('video');
|
|
const canvasElement = document.getElementById('canvas');
|
|
const resultElement = document.getElementById('result');
|
|
const toggleButton = document.getElementById('toggleButton');
|
|
const cameraSelect = document.getElementById('cameraSelect');
|
|
let mediaStream = null;
|
|
let isLiveDetectionActive = false;
|
|
let detectionInterval = null;
|
|
|
|
async function getCameraDevices() {
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
return devices.filter(device => device.kind === 'videoinput');
|
|
}
|
|
|
|
async function setupCamera(cameraId) {
|
|
const constraints = {
|
|
video: {
|
|
deviceId: cameraId
|
|
, facingMode: 'environment'
|
|
}
|
|
};
|
|
mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
videoElement.srcObject = mediaStream;
|
|
}
|
|
|
|
async function toggleLiveDetection() {
|
|
try {
|
|
if (isLiveDetectionActive) {
|
|
if (mediaStream) {
|
|
mediaStream.getTracks().forEach(track => track.stop());
|
|
}
|
|
videoElement.srcObject = null;
|
|
resultElement.style.display = 'none';
|
|
clearInterval(detectionInterval); // Stop detection interval
|
|
} else {
|
|
const cameras = await getCameraDevices();
|
|
if (cameras.length > 0) {
|
|
await setupCamera(cameras[0].deviceId);
|
|
videoElement.srcObject = mediaStream;
|
|
resultElement.style.display = 'block';
|
|
detectionInterval = setInterval(captureAndSendFrame, 1000); // Start detection interval
|
|
} else {
|
|
console.error('No cameras available.');
|
|
}
|
|
}
|
|
isLiveDetectionActive = !isLiveDetectionActive;
|
|
} catch (error) {
|
|
console.error('Error accessing the camera: ', error);
|
|
}
|
|
}
|
|
|
|
toggleButton.addEventListener('click', toggleLiveDetection);
|
|
|
|
cameraSelect.addEventListener('change', async (event) => {
|
|
if (mediaStream) {
|
|
mediaStream.getTracks().forEach(track => track.stop());
|
|
}
|
|
await setupCamera(event.target.value);
|
|
videoElement.srcObject = mediaStream;
|
|
});
|
|
|
|
async function populateCameraList() {
|
|
const cameras = await getCameraDevices();
|
|
cameras.forEach(camera => {
|
|
const option = document.createElement('option');
|
|
option.value = camera.deviceId;
|
|
option.text = camera.label || `Camera ${camera.deviceId}`;
|
|
cameraSelect.appendChild(option);
|
|
});
|
|
}
|
|
|
|
populateCameraList().catch(console.error);
|
|
|
|
function captureAndSendFrame() {
|
|
if (!isLiveDetectionActive) return; // Prevent detection if not active
|
|
const canvasContext = canvasElement.getContext('2d');
|
|
canvasContext.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
|
|
const data = new FormData();
|
|
data.append('image', dataURItoBlob(canvasElement.toDataURL()));
|
|
|
|
fetch('http://127.0.0.1:5000/predict/hewan', {
|
|
method: 'POST'
|
|
, body: data
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.prediction) {
|
|
resultElement.innerHTML =
|
|
`<div class="alert alert-success alert-dismissible fade show" role="alert"><strong>Hasil Klasifikasi: </strong>${data.prediction}
|
|
<br>Confidence: ${data.confidence.toFixed(2)}%
|
|
</div>
|
|
`;
|
|
} else {
|
|
resultElement.innerHTML =
|
|
`<div class="alert alert-danger alert-dismissible fade show" role="alert"><strong>Hasil Klasifikasi: </strong>Tidak dapat mengenali gambar</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error sending frame to API: ', error);
|
|
});
|
|
}
|
|
|
|
function dataURItoBlob(dataURI) {
|
|
const byteString = atob(dataURI.split(',')[1]);
|
|
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
|
const ab = new ArrayBuffer(byteString.length);
|
|
const ia = new Uint8Array(ab);
|
|
for (let i = 0; i < byteString.length; i++) {
|
|
ia[i] = byteString.charCodeAt(i);
|
|
}
|
|
return new Blob([ab], {
|
|
type: mimeString
|
|
});
|
|
}
|
|
|
|
// Initial hide resultElement
|
|
resultElement.style.display = 'none';
|
|
|
|
$(document).ready(function() {
|
|
|
|
$('#fileInput').on('change', function() {
|
|
|
|
var fileInput = this;
|
|
|
|
if (!fileInput.files || fileInput.files.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var formData = new FormData($('#predictForm')[0]);
|
|
var resultDiv = $('#result2');
|
|
resultDiv.empty();
|
|
resultDiv.append(`
|
|
<div class="alert alert-info alert-dismissible fade show">
|
|
<strong>Info!</strong> Sedang memproses gambar...
|
|
</div>
|
|
|
|
|
|
`);
|
|
|
|
$.ajax({
|
|
type: 'POST'
|
|
, url: '/predict/hewan', // Ubah URL ini jika perlu
|
|
data: formData
|
|
, contentType: false
|
|
, processData: false
|
|
, success: function(response) {
|
|
var hasil = response.hasil;
|
|
var gambar = response.gambar;
|
|
var hewan = response.hewan;
|
|
var confidence = response.confidence;
|
|
|
|
|
|
// Fungsi untuk menghindari karakter berbahaya
|
|
function escapeHTML(text) {
|
|
return text.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
|
|
// Clear previous results
|
|
resultDiv.empty();
|
|
|
|
if (hasil) {
|
|
const descId = `desc-${Math.random().toString(36).substring(2, 8)}`; // ID unik
|
|
|
|
resultDiv.append(`
|
|
<img height="200px" width="200px" src="/images/hewan/${gambar}" alt="" style="border-radius: 15px;">
|
|
<div>
|
|
<div class="testi-style1">
|
|
<h3 class="testi-name h2">${escapeHTML(hewan.name)}</h3>
|
|
<p class="testi-text">Confidence: ${confidence.toFixed(2)}%</p>
|
|
<br>
|
|
<p class="testi-text" id="${descId}">${escapeHTML(hewan.description)}</p>
|
|
<br>
|
|
<button type="button" onclick="toggleSpeech(this, '${descId}')" class="btn vs-btn">🔊 Play</button>
|
|
|
|
</div>
|
|
</div>
|
|
`);
|
|
} else {
|
|
resultDiv.append(`
|
|
<div class="alert alert-info alert-dismissible fade show" role="alert"><strong>Hasil Klasifiksi: </strong>Tidak dapat mengenali objek</div>
|
|
`);
|
|
}
|
|
}
|
|
, error: function(xhr, status, error) {
|
|
console.error(xhr.responseText);
|
|
resultDiv.append(`
|
|
<div class="alert alert-info alert-dismissible fade show" role="alert"><strong>Terjadi Kesalahan </strong></div>
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
</script>
|
|
@endsection
|