This document describes the Flask API endpoints for the Rice Object Detection system.
Local Development:
http://localhost:5000
Production:
http://YOUR_SERVER_IP:5000
https://your-domain.com
Returns a welcome message confirming the API is running.
Request:
curl http://localhost:5000/Response:
Welcome to the Rice Quality Detection API! Send a POST request to /predict with an image.
Status Code: 200 OK
Analyzes a rice grain image and returns detection results.
Request Headers:
Content-Type: image/jpeg
Request Body:
- Raw JPEG image data (binary)
Example with cURL:
curl -X POST \
-H "Content-Type: image/jpeg" \
--data-binary @rice_sample.jpg \
http://localhost:5000/predictExample with Python:
import requests
url = "http://localhost:5000/predict"
headers = {"Content-Type": "image/jpeg"}
with open("rice_sample.jpg", "rb") as f:
image_data = f.read()
response = requests.post(url, headers=headers, data=image_data)
print(response.json())Example with ESP32-CAM (Arduino):
HTTPClient http;
http.begin("http://YOUR_SERVER_IP:5000/predict");
http.addHeader("Content-Type", "image/jpeg");
int httpResponseCode = http.POST(fb->buf, fb->len);
String response = http.getString();Success Response (200 OK):
{
"detections": [
{
"box": [120, 85, 245, 210],
"confidence": 0.87,
"label": "Damaged"
},
{
"box": [300, 150, 425, 275],
"confidence": 0.92,
"label": "Discolored"
},
{
"box": [50, 200, 175, 325],
"confidence": 0.78,
"label": "Good"
}
],
"bad_rice_detected": true
}Response Fields:
| Field | Type | Description |
|---|---|---|
detections |
Array | List of all detected objects in the image |
detections[].box |
Array[int] | Bounding box coordinates [x1, y1, x2, y2] |
detections[].confidence |
Float | Detection confidence score (0.0 to 1.0) |
detections[].label |
String | Detected class name |
bad_rice_detected |
Boolean | true if any defective rice is detected, false otherwise |
Bounding Box Format:
x1, y1: Top-left corner coordinatesx2, y2: Bottom-right corner coordinates- Coordinates are in pixels relative to the input image
415 Unsupported Media Type:
{
"error": "Unsupported Media Type. Expected image/jpeg"
}Cause: Incorrect Content-Type header or non-JPEG image data
Solution: Ensure Content-Type: image/jpeg header is set and image is in JPEG format
400 Bad Request:
{
"error": "Invalid image data"
}Cause: Corrupted or unreadable image data
Solution: Verify image is valid JPEG and not corrupted during transmission
The model detects the following rice quality classes:
| Class ID | Label | Description | Triggers Relay |
|---|---|---|---|
| 0 | Good | Healthy rice grain | ❌ No |
| 1 | Damaged | Physically damaged grain | ✅ Yes |
| 2 | Discolored | Color abnormalities | ✅ Yes |
| 3 | Broken | Fractured or incomplete grain | ✅ Yes |
| 4 | Chalky | Opaque or chalky appearance | ✅ Yes |
| 5 | Organic Foreign Matters | Non-rice contaminants | ✅ Yes |
Note
The bad_rice_detected flag is set to true if any of the defect classes (Damaged, Discolored, Broken, Chalky, Organic Foreign Matters) are detected.
To change which classes trigger the relay, edit server/app.py:
BAD_RICE_LABELS = ['Damaged', 'Discolored', 'Broken', 'Chalky', 'Organic Foreign Matters']Example: To only trigger on "Damaged" and "Broken":
BAD_RICE_LABELS = ['Damaged', 'Broken']Current model settings in app.py:
MODEL_PATH = '../models/best.pt' # Path to YOLOv8 modelTo use a different model:
MODEL_PATH = '../models/your_custom_model.pt'The server logs performance metrics for each request:
Image reception and decoding time: 0.0234 seconds
Model inference time: 0.1456 seconds
Total processing time on server: 0.1690 seconds
Detected 3 objects. Bad rice detected: True
--------------------------------------------------
Typical Performance:
- Image Reception: 10-50ms
- Model Inference: 100-500ms (CPU), 20-100ms (GPU)
- Total Processing: 150-600ms
// Capture image
camera_fb_t *fb = esp_camera_fb_get();
// Send to server
HTTPClient http;
http.begin(SERVER_URL);
http.addHeader("Content-Type", "image/jpeg");
int httpResponseCode = http.POST(fb->buf, fb->len);
if (httpResponseCode > 0) {
String response = http.getString();
// Parse JSON
StaticJsonDocument<512> doc;
deserializeJson(doc, response);
bool badRiceDetected = doc["bad_rice_detected"] | false;
if (badRiceDetected) {
// Activate relay
digitalWrite(RELAY_GPIO_NUM, HIGH);
delay(1000);
digitalWrite(RELAY_GPIO_NUM, LOW);
}
}
http.end();
esp_camera_fb_return(fb);import requests
import json
def detect_rice_quality(image_path, server_url):
"""
Send image to rice detection API and return results.
Args:
image_path: Path to JPEG image file
server_url: API endpoint URL
Returns:
dict: Detection results
"""
headers = {"Content-Type": "image/jpeg"}
with open(image_path, "rb") as f:
image_data = f.read()
response = requests.post(server_url, headers=headers, data=image_data)
if response.status_code == 200:
results = response.json()
print(f"Detections: {len(results['detections'])}")
print(f"Bad rice detected: {results['bad_rice_detected']}")
return results
else:
print(f"Error: {response.status_code} - {response.text}")
return None
# Usage
results = detect_rice_quality(
"rice_sample.jpg",
"http://localhost:5000/predict"
)const fs = require('fs');
const axios = require('axios');
async function detectRiceQuality(imagePath, serverUrl) {
const imageBuffer = fs.readFileSync(imagePath);
try {
const response = await axios.post(serverUrl, imageBuffer, {
headers: {
'Content-Type': 'image/jpeg'
}
});
console.log('Detections:', response.data.detections.length);
console.log('Bad rice detected:', response.data.bad_rice_detected);
return response.data;
} catch (error) {
console.error('Error:', error.message);
return null;
}
}
// Usage
detectRiceQuality(
'rice_sample.jpg',
'http://localhost:5000/predict'
);Currently, there is no rate limiting implemented. For production use, consider adding rate limiting to prevent abuse:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["100 per hour", "10 per minute"]
)
@app.route('/predict', methods=['POST'])
@limiter.limit("30 per minute")
def predict():
# ... existing codeCurrently, the API is open (no authentication required). For production deployment, consider implementing:
API_TOKEN = "your-secret-token"
@app.route('/predict', methods=['POST'])
def predict():
token = request.headers.get('Authorization')
if token != f"Bearer {API_TOKEN}":
return jsonify({"error": "Unauthorized"}), 401
# ... existing codeClient usage:
curl -X POST \
-H "Content-Type: image/jpeg" \
-H "Authorization: Bearer your-secret-token" \
--data-binary @rice_sample.jpg \
http://localhost:5000/predictALLOWED_IPS = ['192.168.1.100', '10.0.0.50']
@app.route('/predict', methods=['POST'])
def predict():
if request.remote_addr not in ALLOWED_IPS:
return jsonify({"error": "Forbidden"}), 403
# ... existing code-
Create New Request
- Method:
POST - URL:
http://localhost:5000/predict
- Method:
-
Set Headers
- Key:
Content-Type - Value:
image/jpeg
- Key:
-
Set Body
- Type:
binary - Select a JPEG image file
- Type:
-
Send Request
Test with sample rice images:
# Download sample image
curl -o rice_sample.jpg https://example.com/rice_sample.jpg
# Test API
curl -X POST \
-H "Content-Type: image/jpeg" \
--data-binary @rice_sample.jpg \
http://localhost:5000/predict{
"detections": [],
"bad_rice_detected": false
}Possible Causes:
- No rice grains in image
- Image quality too poor
- Model confidence threshold not met
- Incorrect camera positioning
Solutions:
- Improve lighting
- Adjust camera distance
- Ensure rice grains are visible
- Lower confidence threshold in model
Causes:
- Large image size
- CPU inference (no GPU)
- Network latency
Solutions:
- Reduce image resolution from ESP32-CAM
- Use GPU-enabled server
- Optimize model (quantization)
- Deploy server closer to ESP32-CAM
For hardware setup, see: Hardware Setup Guide
For deployment instructions, see: Deployment Guide