Compare commits

..

No commits in common. "3dc1bcf466fdfd4b5aa459a1d6ce202b21eac319" and "5397b09a1324338e6263413c296d41dbfbea3ba9" have entirely different histories.

2 changed files with 248 additions and 345 deletions

View File

@ -1,169 +1,70 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>TomAIto</title> <title>YOLOv8 Object Detection</title>
<style> <style>
canvas { canvas {
border: 1px solid black; display:block;
display: flex; border: 1px solid black;
flex-direction: column; margin-top:10px;
max-width: 300px; /* Ustaw maksymalną szerokość formularza */ }
margin: auto; /* Centruj formularz na stronie */ </style>
margin-top: 10px; </head>
} <body>
<input id="uploadInput" type="file"/>
#weatherInfo { <canvas></canvas>
display: flex; <script>
flex-direction: column; /**
max-width: 300px; /* Ustaw maksymalną szerokość formularza */ * "Upload" button onClick handler: uploads selected
margin: auto; /* Centruj formularz na stronie */ * image file to backend, receives an array of
margin-top: 10px; * detected objects and draws them on top of image
} */
const input = document.getElementById("uploadInput");
input.addEventListener("change",async(event) => {
form { const file = event.target.files[0];
display: flex; const data = new FormData();
flex-direction: column; data.append("image_file",file,"image_file");
max-width: 300px; /* Ustaw maksymalną szerokość formularza */ const response = await fetch("/detect",{
margin: auto; /* Centruj formularz na stronie */ method:"post",
} body:data
});
label { const boxes = await response.json();
margin-top: 10px; draw_image_and_boxes(file,boxes);
} })
button { /**
margin-top: 10px; * Function draws the image from provided file
} * and bounding boxes of detected objects on
</style> * top of the image
</head> * @param file Uploaded file object
<body> * @param boxes Array of bounding boxes in format
<form id="uploadForm"> [[x1,y1,x2,y2,object_type,probability],...]
*/
<label for="cropType">Crop Type:</label> function draw_image_and_boxes(file,boxes) {
<select id="cropType" name="cropType"> const img = new Image()
<option value="external">External</option> img.src = URL.createObjectURL(file);
<option value="internal">Internal</option> img.onload = () => {
<option value="greenhouse">Greenhouse</option> const canvas = document.querySelector("canvas");
</select> canvas.width = img.width;
canvas.height = img.height;
<label for="location">Location:</label> const ctx = canvas.getContext("2d");
<input type="text" id="location" name="location" required/> ctx.drawImage(img,0,0);
ctx.strokeStyle = "#00FF00";
<label for="variety">Variety:</label> ctx.lineWidth = 5;
<input type="text" id="variety" name="variety" required/> ctx.font = "20px serif";
boxes.forEach(([x1,y1,x2,y2,object_type, prob]) => {
<label for="uploadInput">Choose an image:</label>
<input id="uploadInput" type="file" required/> const label = `${object_type} ${prob.toFixed(2)}`;
ctx.strokeRect(x1,y1,x2-x1,y2-y1);
<button type="Sprawdz">Sprawdz</button> ctx.fillStyle = "#00ff00";
</form> const width = ctx.measureText(label).width;
ctx.fillRect(x1,y1,width+10,25);
<canvas></canvas> ctx.fillStyle = "#000000";
<div id="weatherInfo"></div> ctx.fillText(label,x1,y1+18);
<script> });
const form = document.getElementById("uploadForm"); }
}
form.addEventListener("submit", async (event) => { </script>
event.preventDefault(); </body>
const fileInput = document.getElementById("uploadInput");
const cropTypeInput = document.getElementById("cropType");
const locationInput = document.getElementById("location");
const varietyInput = document.getElementById("variety");
const file = fileInput.files[0];
const data = new FormData();
data.append("image_file", file);
data.append("cropType", cropTypeInput.value);
data.append("location", locationInput.value);
data.append("variety", varietyInput.value);
const response = await fetch("/detect", {
method: "post",
body: data
});
const boxes = await response.json();
draw_image_and_boxes(file, boxes);
const location = locationInput.value;
if (cropTypeInput.value == "external") {
getWeather(location);
}
else {
const randomTips = [
"water your tomatoes every day.",
"keep the temperature between 20-25 degrees Celsius.",
"place the tomatoes in sunlight."];
const randomTip = randomTips[Math.floor(Math.random() * randomTips.length)];
document.getElementById("weatherInfo").innerText = "TIP! To maximalize your yields " + randomTip;
}
});
function draw_image_and_boxes(file, boxes) {
const img = new Image()
img.src = URL.createObjectURL(file);
img.onload = () => {
const canvas = document.querySelector("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
ctx.strokeStyle = "#00FF00";
ctx.lineWidth = 5;
ctx.font = "20px serif";
boxes.forEach(([x1, y1, x2, y2, object_type, prob]) => {
const label = `${object_type} ${prob.toFixed(2)}`;
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
ctx.fillStyle = "#00ff00";
const width = ctx.measureText(label).width;
ctx.fillRect(x1, y1, width + 10, 25);
ctx.fillStyle = "#000000";
ctx.fillText(label, x1, y1 + 18);
});
}
}
async function getWeather(location) {
const apiKey = "1fc6a52c96331d035a828cc0c1606241";
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${location}&appid=${apiKey}`;
try {
const weatherResponse = await fetch(apiUrl);
const weatherData = await weatherResponse.json();
if (weatherData.main && weatherData.main.temp) {
const temperatureKelvin = weatherData.main.temp;
const temperatureCelsius = temperatureKelvin - 273.15;
const tempRound = Math.round(temperatureCelsius);
const weatherCondition = weatherData.weather[0].main;
const isRaining = weatherCondition === 'Rain';
let additionalInfo = `${isRaining ? 'Rain is expected in your town today, so you dont have to water your tomatoes' :
'No rainfall is expected in the city today, so you need to water your tomatoes!'} Temperature in ${location} is ${tempRound} °C.`;
if (tempRound < 15) {
additionalInfo += ' So you need to cover your tomatoes!';
}
document.getElementById("weatherInfo").innerText = additionalInfo;
} else {
document.getElementById("weatherInfo").innerText = "Unable to fetch weather data.";
}
} catch (error) {
console.error("Error fetching weather data:", error);
document.getElementById("weatherInfo").innerText = "Error fetching weather data.";
}
}
</script>
</body>
</html> </html>

View File

@ -1,177 +1,179 @@
#from ultralytics import YOLO #from ultralytics import YOLO
from flask import request, Flask, jsonify from flask import request, Flask, jsonify
from waitress import serve from waitress import serve
from PIL import Image from PIL import Image
import onnxruntime as ort import onnxruntime as ort
import numpy as np import numpy as np
import requests
#my changes
#my changes import os
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
script_dir = os.path.dirname(os.path.abspath(__file__)) # Change the working directory to the script's directory
# Change the working directory to the script's directory os.chdir(script_dir)
os.chdir(script_dir)
yolo_classes = ["b_fully_ripened",
yolo_classes = ["b_fully_ripened", "b_half_ripened",
"b_half_ripened", "b_green",
"b_green", "l_fully_ripened",
"l_fully_ripened", "l_half_ripened",
"l_half_ripened", "l_green"
"l_green" ]
]
#app start
#app start
app = Flask(__name__)
app = Flask(__name__)
@app.route("/")
def root():
@app.route("/") """
def root(): Site main page handler function.
""" :return: Content of index.html file
Site main page handler function. """
:return: Content of index.html file with open("index.html") as file:
""" return file.read()
with open("index.html") as file:
return file.read()
@app.route("/detect", methods=["POST"])
@app.route("/detect", methods=["POST"]) def detect():
def detect(): """
buf = request.files["image_file"] Handler of /detect POST endpoint
crop_type = request.form.get("cropType") Receives uploaded file with a name "image_file", passes it
location = request.form.get("location") through YOLOv8 object detection network and returns and array
variety = request.form.get("variety") of bounding boxes.
boxes, orientation = detect_objects_on_image(buf.stream) :return: a JSON array of objects bounding boxes in format [[x1,y1,x2,y2,object_type,probability],..]
# Do something with crop_type, location, and variety here (e.g., store in database) """
buf = request.files["image_file"]
return jsonify(boxes) boxes, orientation = detect_objects_on_image(buf.stream)
#print(boxes)
#print(orientation)
def detect_objects_on_image(buf): return jsonify(boxes)
input, img_width, img_height = prepare_input(buf)
output = run_model(input) def detect_objects_on_image(buf):
orientation = get_orientation(buf) input, img_width, img_height = prepare_input(buf)
processed_output = process_output(output, img_width, img_height, orientation) output = run_model(input)
return processed_output, orientation orientation = get_orientation(buf)
processed_output = process_output(output, img_width, img_height, orientation)
def prepare_input(buf): return processed_output, orientation
img = Image.open(buf)
img_width, img_height = img.size def prepare_input(buf):
img = img.resize((640, 640)) img = Image.open(buf)
img = img.convert("RGB") img_width, img_height = img.size
input = np.array(img) img = img.resize((640, 640))
input = input.transpose(2, 0, 1) img = img.convert("RGB")
input = input.reshape(1, 3, 640, 640) / 255.0 input = np.array(img)
return input.astype(np.float32), img_width, img_height input = input.transpose(2, 0, 1)
input = input.reshape(1, 3, 640, 640) / 255.0
def run_model(input): return input.astype(np.float32), img_width, img_height
model = ort.InferenceSession("best.onnx", providers=['CPUExecutionProvider'])
outputs = model.run(["output0"], {"images":input}) def run_model(input):
return outputs[0] model = ort.InferenceSession("best.onnx", providers=['CPUExecutionProvider'])
outputs = model.run(["output0"], {"images":input})
def process_output(output, img_width, img_height, orientation): return outputs[0]
output = output[0].astype(float)
output = output.transpose() def process_output(output, img_width, img_height, orientation):
output = output[0].astype(float)
boxes = [] output = output.transpose()
for row in output:
prob = row[4:].max() boxes = []
if prob < 0.5: for row in output:
continue prob = row[4:].max()
if prob < 0.5:
class_id = row[4:].argmax() continue
label = yolo_classes[class_id]
xc, yc, w, h = row[:4] class_id = row[4:].argmax()
x1 = (xc - w/2) / 640 * img_width label = yolo_classes[class_id]
y1 = (yc - h/2) / 640 * img_height xc, yc, w, h = row[:4]
x2 = (xc + w/2) / 640 * img_width x1 = (xc - w/2) / 640 * img_width
y2 = (yc + h/2) / 640 * img_height y1 = (yc - h/2) / 640 * img_height
x2 = (xc + w/2) / 640 * img_width
boxes.append([x1, y1, x2, y2, label, prob]) y2 = (yc + h/2) / 640 * img_height
# Adjust boxes based on orientation boxes.append([x1, y1, x2, y2, label, prob])
adjusted_boxes = adjust_boxes_for_orientation(boxes, orientation, img_width, img_height)
# Adjust boxes based on orientation
# Sort and apply non-max suppression as before adjusted_boxes = adjust_boxes_for_orientation(boxes, orientation, img_width, img_height)
adjusted_boxes.sort(key=lambda x: x[5], reverse=True)
result = [] # Sort and apply non-max suppression as before
while len(adjusted_boxes) > 0: adjusted_boxes.sort(key=lambda x: x[5], reverse=True)
result.append(adjusted_boxes[0]) result = []
adjusted_boxes = [box for box in adjusted_boxes if iou(box, adjusted_boxes[0]) < 0.7] while len(adjusted_boxes) > 0:
result.append(adjusted_boxes[0])
return result adjusted_boxes = [box for box in adjusted_boxes if iou(box, adjusted_boxes[0]) < 0.7]
return result
def iou(box1,box2):
return intersection(box1,box2)/union(box1,box2)
def iou(box1,box2):
def union(box1,box2): return intersection(box1,box2)/union(box1,box2)
box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4]
box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4] def union(box1,box2):
box1_area = (box1_x2-box1_x1)*(box1_y2-box1_y1) box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4]
box2_area = (box2_x2-box2_x1)*(box2_y2-box2_y1) box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4]
return box1_area + box2_area - intersection(box1,box2) box1_area = (box1_x2-box1_x1)*(box1_y2-box1_y1)
box2_area = (box2_x2-box2_x1)*(box2_y2-box2_y1)
def intersection(box1,box2): return box1_area + box2_area - intersection(box1,box2)
box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4]
box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4] def intersection(box1,box2):
x1 = max(box1_x1,box2_x1) box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4]
y1 = max(box1_y1,box2_y1) box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4]
x2 = min(box1_x2,box2_x2) x1 = max(box1_x1,box2_x1)
y2 = min(box1_y2,box2_y2) y1 = max(box1_y1,box2_y1)
return (x2-x1)*(y2-y1) x2 = min(box1_x2,box2_x2)
y2 = min(box1_y2,box2_y2)
def get_orientation(image_path): return (x2-x1)*(y2-y1)
with Image.open(image_path) as img:
if hasattr(img, '_getexif'): def get_orientation(image_path):
exif_data = img._getexif() with Image.open(image_path) as img:
if exif_data is not None: if hasattr(img, '_getexif'):
return exif_data.get(274, 1) # Default to normal orientation exif_data = img._getexif()
return 1 # Default orientation if no EXIF data if exif_data is not None:
return exif_data.get(274, 1) # Default to normal orientation
def adjust_boxes_for_orientation(boxes, orientation, img_width, img_height): return 1 # Default orientation if no EXIF data
adjusted_boxes = []
for box in boxes: def adjust_boxes_for_orientation(boxes, orientation, img_width, img_height):
x1, y1, x2, y2, label, prob = box adjusted_boxes = []
for box in boxes:
# Apply transformations based on orientation x1, y1, x2, y2, label, prob = box
if orientation == 3: # 180 degrees
x1, y1, x2, y2 = img_width - x2, img_height - y2, img_width - x1, img_height - y1 # Apply transformations based on orientation
elif orientation == 6: # 270 degrees (or -90 degrees) if orientation == 3: # 180 degrees
x1, y1, x2, y2 = img_height - y2, x1, img_height - y1, x2 x1, y1, x2, y2 = img_width - x2, img_height - y2, img_width - x1, img_height - y1
elif orientation == 8: # 90 degrees elif orientation == 6: # 270 degrees (or -90 degrees)
x1, y1, x2, y2 = y1, img_width - x2, y2, img_width - x1 x1, y1, x2, y2 = img_height - y2, x1, img_height - y1, x2
elif orientation == 8: # 90 degrees
adjusted_boxes.append([x1, y1, x2, y2, label, prob]) x1, y1, x2, y2 = y1, img_width - x2, y2, img_width - x1
return adjusted_boxes adjusted_boxes.append([x1, y1, x2, y2, label, prob])
return adjusted_boxes
""" def detect_objects_on_image(buf):
"""
"""" """ def detect_objects_on_image(buf):
Function receives an image, """
passes it through YOLOv8 neural network """"
and returns an array of detected objects Function receives an image,
and their bounding boxes passes it through YOLOv8 neural network
:param buf: Input image file stream and returns an array of detected objects
:return: Array of bounding boxes in format [[x1,y1,x2,y2,object_type,probability],..] and their bounding boxes
""" :param buf: Input image file stream
""""" :return: Array of bounding boxes in format [[x1,y1,x2,y2,object_type,probability],..]
model = YOLO("best.pt") """
results = model.predict(Image.open(buf)) """""
result = results[0] model = YOLO("best.pt")
output = [] results = model.predict(Image.open(buf))
for box in result.boxes: result = results[0]
x1, y1, x2, y2 = [ output = []
round(x) for x in box.xyxy[0].tolist() for box in result.boxes:
] x1, y1, x2, y2 = [
class_id = box.cls[0].item() round(x) for x in box.xyxy[0].tolist()
prob = round(box.conf[0].item(), 2) ]
output.append([ class_id = box.cls[0].item()
x1, y1, x2, y2, result.names[class_id], prob prob = round(box.conf[0].item(), 2)
]) output.append([
return output x1, y1, x2, y2, result.names[class_id], prob
])
""" return output
serve(app, host='0.0.0.0', port=8080)
"""
serve(app, host='0.0.0.0', port=8080)