Compare commits

...

2 Commits

Author SHA1 Message Date
3dc1bcf466 Crop type affects the output 2024-01-16 16:02:04 +01:00
d8ac8c32a5 Weather api added 2024-01-16 15:30:46 +01:00
2 changed files with 345 additions and 248 deletions

View File

@ -1,70 +1,169 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>YOLOv8 Object Detection</title> <title>TomAIto</title>
<style> <style>
canvas { canvas {
display:block; border: 1px solid black;
border: 1px solid black; display: flex;
margin-top:10px; flex-direction: column;
} max-width: 300px; /* Ustaw maksymalną szerokość formularza */
</style> margin: auto; /* Centruj formularz na stronie */
</head> margin-top: 10px;
<body> }
<input id="uploadInput" type="file"/>
<canvas></canvas> #weatherInfo {
<script> display: flex;
/** flex-direction: column;
* "Upload" button onClick handler: uploads selected max-width: 300px; /* Ustaw maksymalną szerokość formularza */
* image file to backend, receives an array of margin: auto; /* Centruj formularz na stronie */
* detected objects and draws them on top of image margin-top: 10px;
*/ }
const input = document.getElementById("uploadInput");
input.addEventListener("change",async(event) => {
const file = event.target.files[0]; form {
const data = new FormData(); display: flex;
data.append("image_file",file,"image_file"); flex-direction: column;
const response = await fetch("/detect",{ max-width: 300px; /* Ustaw maksymalną szerokość formularza */
method:"post", margin: auto; /* Centruj formularz na stronie */
body:data }
});
const boxes = await response.json(); label {
draw_image_and_boxes(file,boxes); margin-top: 10px;
}) }
/** button {
* Function draws the image from provided file margin-top: 10px;
* and bounding boxes of detected objects on }
* top of the image </style>
* @param file Uploaded file object </head>
* @param boxes Array of bounding boxes in format <body>
[[x1,y1,x2,y2,object_type,probability],...] <form id="uploadForm">
*/
function draw_image_and_boxes(file,boxes) { <label for="cropType">Crop Type:</label>
const img = new Image() <select id="cropType" name="cropType">
img.src = URL.createObjectURL(file); <option value="external">External</option>
img.onload = () => { <option value="internal">Internal</option>
const canvas = document.querySelector("canvas"); <option value="greenhouse">Greenhouse</option>
canvas.width = img.width; </select>
canvas.height = img.height;
const ctx = canvas.getContext("2d"); <label for="location">Location:</label>
ctx.drawImage(img,0,0); <input type="text" id="location" name="location" required/>
ctx.strokeStyle = "#00FF00";
ctx.lineWidth = 5; <label for="variety">Variety:</label>
ctx.font = "20px serif"; <input type="text" id="variety" name="variety" required/>
boxes.forEach(([x1,y1,x2,y2,object_type, prob]) => {
<label for="uploadInput">Choose an image:</label>
const label = `${object_type} ${prob.toFixed(2)}`; <input id="uploadInput" type="file" required/>
ctx.strokeRect(x1,y1,x2-x1,y2-y1);
ctx.fillStyle = "#00ff00"; <button type="Sprawdz">Sprawdz</button>
const width = ctx.measureText(label).width; </form>
ctx.fillRect(x1,y1,width+10,25);
ctx.fillStyle = "#000000"; <canvas></canvas>
ctx.fillText(label,x1,y1+18); <div id="weatherInfo"></div>
}); <script>
} const form = document.getElementById("uploadForm");
}
</script> form.addEventListener("submit", async (event) => {
</body> event.preventDefault();
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,179 +1,177 @@
#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
import os #my changes
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
# Change the working directory to the script's directory script_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(script_dir) # Change the working directory to the script's directory
os.chdir(script_dir)
yolo_classes = ["b_fully_ripened",
"b_half_ripened", yolo_classes = ["b_fully_ripened",
"b_green", "b_half_ripened",
"l_fully_ripened", "b_green",
"l_half_ripened", "l_fully_ripened",
"l_green" "l_half_ripened",
] "l_green"
]
#app start
#app start
app = Flask(__name__)
app = Flask(__name__)
@app.route("/")
def root():
""" @app.route("/")
Site main page handler function. def root():
:return: Content of index.html file """
""" Site main page handler function.
with open("index.html") as file: :return: Content of index.html file
return file.read() """
with open("index.html") as file:
return file.read()
@app.route("/detect", methods=["POST"])
def detect(): @app.route("/detect", methods=["POST"])
""" def detect():
Handler of /detect POST endpoint buf = request.files["image_file"]
Receives uploaded file with a name "image_file", passes it crop_type = request.form.get("cropType")
through YOLOv8 object detection network and returns and array location = request.form.get("location")
of bounding boxes. variety = request.form.get("variety")
:return: a JSON array of objects bounding boxes in format [[x1,y1,x2,y2,object_type,probability],..] boxes, orientation = detect_objects_on_image(buf.stream)
""" # Do something with crop_type, location, and variety here (e.g., store in database)
buf = request.files["image_file"]
boxes, orientation = detect_objects_on_image(buf.stream) return jsonify(boxes)
#print(boxes)
#print(orientation)
return jsonify(boxes) def detect_objects_on_image(buf):
input, img_width, img_height = prepare_input(buf)
def detect_objects_on_image(buf): output = run_model(input)
input, img_width, img_height = prepare_input(buf) orientation = get_orientation(buf)
output = run_model(input) processed_output = process_output(output, img_width, img_height, orientation)
orientation = get_orientation(buf) return processed_output, orientation
processed_output = process_output(output, img_width, img_height, orientation)
return processed_output, orientation def prepare_input(buf):
img = Image.open(buf)
def prepare_input(buf): img_width, img_height = img.size
img = Image.open(buf) img = img.resize((640, 640))
img_width, img_height = img.size img = img.convert("RGB")
img = img.resize((640, 640)) input = np.array(img)
img = img.convert("RGB") input = input.transpose(2, 0, 1)
input = np.array(img) input = input.reshape(1, 3, 640, 640) / 255.0
input = input.transpose(2, 0, 1) return input.astype(np.float32), img_width, img_height
input = input.reshape(1, 3, 640, 640) / 255.0
return input.astype(np.float32), img_width, img_height def run_model(input):
model = ort.InferenceSession("best.onnx", providers=['CPUExecutionProvider'])
def run_model(input): outputs = model.run(["output0"], {"images":input})
model = ort.InferenceSession("best.onnx", providers=['CPUExecutionProvider']) return outputs[0]
outputs = model.run(["output0"], {"images":input})
return outputs[0] def process_output(output, img_width, img_height, orientation):
output = output[0].astype(float)
def process_output(output, img_width, img_height, orientation): output = output.transpose()
output = output[0].astype(float)
output = output.transpose() boxes = []
for row in output:
boxes = [] prob = row[4:].max()
for row in output: if prob < 0.5:
prob = row[4:].max() continue
if prob < 0.5:
continue class_id = row[4:].argmax()
label = yolo_classes[class_id]
class_id = row[4:].argmax() xc, yc, w, h = row[:4]
label = yolo_classes[class_id] x1 = (xc - w/2) / 640 * img_width
xc, yc, w, h = row[:4] y1 = (yc - h/2) / 640 * img_height
x1 = (xc - w/2) / 640 * img_width x2 = (xc + w/2) / 640 * img_width
y1 = (yc - h/2) / 640 * img_height y2 = (yc + h/2) / 640 * img_height
x2 = (xc + w/2) / 640 * img_width
y2 = (yc + h/2) / 640 * img_height boxes.append([x1, y1, x2, y2, label, prob])
boxes.append([x1, y1, x2, y2, label, prob]) # Adjust boxes based on orientation
adjusted_boxes = adjust_boxes_for_orientation(boxes, orientation, img_width, img_height)
# Adjust boxes based on orientation
adjusted_boxes = adjust_boxes_for_orientation(boxes, orientation, img_width, img_height) # Sort and apply non-max suppression as before
adjusted_boxes.sort(key=lambda x: x[5], reverse=True)
# Sort and apply non-max suppression as before result = []
adjusted_boxes.sort(key=lambda x: x[5], reverse=True) while len(adjusted_boxes) > 0:
result = [] result.append(adjusted_boxes[0])
while len(adjusted_boxes) > 0: adjusted_boxes = [box for box in adjusted_boxes if iou(box, adjusted_boxes[0]) < 0.7]
result.append(adjusted_boxes[0])
adjusted_boxes = [box for box in adjusted_boxes if iou(box, adjusted_boxes[0]) < 0.7] return result
return result
def iou(box1,box2):
return intersection(box1,box2)/union(box1,box2)
def iou(box1,box2):
return intersection(box1,box2)/union(box1,box2) def union(box1,box2):
box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4]
def union(box1,box2): box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4]
box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4] box1_area = (box1_x2-box1_x1)*(box1_y2-box1_y1)
box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4] box2_area = (box2_x2-box2_x1)*(box2_y2-box2_y1)
box1_area = (box1_x2-box1_x1)*(box1_y2-box1_y1) return box1_area + box2_area - intersection(box1,box2)
box2_area = (box2_x2-box2_x1)*(box2_y2-box2_y1)
return box1_area + box2_area - intersection(box1,box2) def intersection(box1,box2):
box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4]
def intersection(box1,box2): box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4]
box1_x1,box1_y1,box1_x2,box1_y2 = box1[:4] x1 = max(box1_x1,box2_x1)
box2_x1,box2_y1,box2_x2,box2_y2 = box2[:4] y1 = max(box1_y1,box2_y1)
x1 = max(box1_x1,box2_x1) x2 = min(box1_x2,box2_x2)
y1 = max(box1_y1,box2_y1) y2 = min(box1_y2,box2_y2)
x2 = min(box1_x2,box2_x2) return (x2-x1)*(y2-y1)
y2 = min(box1_y2,box2_y2)
return (x2-x1)*(y2-y1) def get_orientation(image_path):
with Image.open(image_path) as img:
def get_orientation(image_path): if hasattr(img, '_getexif'):
with Image.open(image_path) as img: exif_data = img._getexif()
if hasattr(img, '_getexif'): if exif_data is not None:
exif_data = img._getexif() return exif_data.get(274, 1) # Default to normal orientation
if exif_data is not None: return 1 # Default orientation if no EXIF data
return exif_data.get(274, 1) # Default to normal orientation
return 1 # Default orientation if no EXIF data def adjust_boxes_for_orientation(boxes, orientation, img_width, img_height):
adjusted_boxes = []
def adjust_boxes_for_orientation(boxes, orientation, img_width, img_height): for box in boxes:
adjusted_boxes = [] x1, y1, x2, y2, label, prob = box
for box in boxes:
x1, y1, x2, y2, label, prob = box # Apply transformations based on orientation
if orientation == 3: # 180 degrees
# Apply transformations based on orientation x1, y1, x2, y2 = img_width - x2, img_height - y2, img_width - x1, img_height - y1
if orientation == 3: # 180 degrees elif orientation == 6: # 270 degrees (or -90 degrees)
x1, y1, x2, y2 = img_width - x2, img_height - y2, img_width - x1, img_height - y1 x1, y1, x2, y2 = img_height - y2, x1, img_height - y1, x2
elif orientation == 6: # 270 degrees (or -90 degrees) elif orientation == 8: # 90 degrees
x1, y1, x2, y2 = img_height - y2, x1, img_height - y1, x2 x1, y1, x2, y2 = y1, img_width - x2, y2, img_width - x1
elif orientation == 8: # 90 degrees
x1, y1, x2, y2 = y1, img_width - x2, y2, img_width - x1 adjusted_boxes.append([x1, y1, x2, y2, label, prob])
adjusted_boxes.append([x1, y1, x2, y2, label, prob]) return adjusted_boxes
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
Function receives an image, and returns an array of detected objects
passes it through YOLOv8 neural network and their bounding boxes
and returns an array of detected objects :param buf: Input image file stream
and their bounding boxes :return: Array of bounding boxes in format [[x1,y1,x2,y2,object_type,probability],..]
: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))
model = YOLO("best.pt") result = results[0]
results = model.predict(Image.open(buf)) output = []
result = results[0] for box in result.boxes:
output = [] x1, y1, x2, y2 = [
for box in result.boxes: round(x) for x in box.xyxy[0].tolist()
x1, y1, x2, y2 = [ ]
round(x) for x in box.xyxy[0].tolist() class_id = box.cls[0].item()
] prob = round(box.conf[0].item(), 2)
class_id = box.cls[0].item() output.append([
prob = round(box.conf[0].item(), 2) x1, y1, x2, y2, result.names[class_id], prob
output.append([ ])
x1, y1, x2, y2, result.names[class_id], prob return output
])
return output """
serve(app, host='0.0.0.0', port=8080)
"""
serve(app, host='0.0.0.0', port=8080)