141 lines
5.3 KiB
Java
141 lines
5.3 KiB
Java
package com.auction;
|
||
|
||
import org.opencv.core.Mat;
|
||
import org.opencv.core.Scalar;
|
||
import org.opencv.core.Size;
|
||
import org.opencv.dnn.Dnn;
|
||
import org.opencv.dnn.Net;
|
||
import org.opencv.imgcodecs.Imgcodecs;
|
||
import java.io.IOException;
|
||
import java.nio.file.Files;
|
||
import java.nio.file.Path;
|
||
import java.nio.file.Paths;
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import static org.opencv.dnn.Dnn.DNN_BACKEND_OPENCV;
|
||
import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
|
||
/**
|
||
* Service for performing object detection on images using OpenCV's DNN
|
||
* module. The DNN module can load pre‑trained models from several
|
||
* frameworks (Darknet, TensorFlow, ONNX, etc.)【784097309529506†L209-L233】. Here
|
||
* we load a YOLO model (Darknet) by specifying the configuration and
|
||
* weights files. For each image we run a forward pass and return a
|
||
* list of detected class labels.
|
||
*
|
||
* If model files are not found, the service operates in disabled mode
|
||
* and returns empty lists.
|
||
*/
|
||
class ObjectDetectionService {
|
||
|
||
private final Net net;
|
||
private final List<String> classNames;
|
||
private final boolean enabled;
|
||
|
||
ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException {
|
||
// Check if model files exist
|
||
var cfgFile = Paths.get(cfgPath);
|
||
var weightsFile = Paths.get(weightsPath);
|
||
var classNamesFile = Paths.get(classNamesPath);
|
||
|
||
if (!Files.exists(cfgFile) || !Files.exists(weightsFile) || !Files.exists(classNamesFile)) {
|
||
IO.println("⚠️ Object detection disabled: YOLO model files not found");
|
||
IO.println(" Expected files:");
|
||
IO.println(" - " + cfgPath);
|
||
IO.println(" - " + weightsPath);
|
||
IO.println(" - " + classNamesPath);
|
||
IO.println(" Scraper will continue without image analysis.");
|
||
this.enabled = false;
|
||
this.net = null;
|
||
this.classNames = new ArrayList<>();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Load network
|
||
this.net = Dnn.readNetFromDarknet(cfgPath, weightsPath);
|
||
this.net.setPreferableBackend(DNN_BACKEND_OPENCV);
|
||
this.net.setPreferableTarget(DNN_TARGET_CPU);
|
||
// Load class names (one per line)
|
||
this.classNames = Files.readAllLines(classNamesFile);
|
||
this.enabled = true;
|
||
IO.println("✓ Object detection enabled with YOLO");
|
||
} catch (Exception e) {
|
||
System.err.println("⚠️ Object detection disabled: " + e.getMessage());
|
||
throw new IOException("Failed to initialize object detection", e);
|
||
}
|
||
}
|
||
/**
|
||
* Detects objects in the given image file and returns a list of
|
||
* human‑readable labels. Only detections above a confidence
|
||
* threshold are returned. For brevity this method omits drawing
|
||
* bounding boxes. See the OpenCV DNN documentation for details on
|
||
* post‑processing【784097309529506†L324-L344】.
|
||
*
|
||
* @param imagePath absolute path to the image
|
||
* @return list of detected class names (empty if detection disabled)
|
||
*/
|
||
List<String> detectObjects(String imagePath) {
|
||
if (!enabled) {
|
||
return new ArrayList<>();
|
||
}
|
||
|
||
List<String> labels = new ArrayList<>();
|
||
var image = Imgcodecs.imread(imagePath);
|
||
if (image.empty()) return labels;
|
||
// Create a 4D blob from the image
|
||
var blob = Dnn.blobFromImage(image, 1.0 / 255.0, new Size(416, 416), new Scalar(0, 0, 0), true, false);
|
||
net.setInput(blob);
|
||
List<Mat> outs = new ArrayList<>();
|
||
var outNames = getOutputLayerNames(net);
|
||
net.forward(outs, outNames);
|
||
// Post‑process: for each detection compute score and choose class
|
||
var confThreshold = 0.5f;
|
||
for (var out : outs) {
|
||
for (var i = 0; i < out.rows(); i++) {
|
||
var data = out.get(i, 0);
|
||
if (data == null) continue;
|
||
// The first 5 numbers are bounding box, then class scores
|
||
var scores = new double[classNames.size()];
|
||
System.arraycopy(data, 5, scores, 0, scores.length);
|
||
var classId = argMax(scores);
|
||
var confidence = scores[classId];
|
||
if (confidence > confThreshold) {
|
||
var label = classNames.get(classId);
|
||
if (!labels.contains(label)) {
|
||
labels.add(label);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return labels;
|
||
}
|
||
/**
|
||
* Returns the indexes of the output layers in the network. YOLO
|
||
* automatically discovers its output layers; other models may require
|
||
* manually specifying them【784097309529506†L356-L365】.
|
||
*/
|
||
private List<String> getOutputLayerNames(Net net) {
|
||
List<String> names = new ArrayList<>();
|
||
var outLayers = net.getUnconnectedOutLayers().toList();
|
||
var layersNames = net.getLayerNames();
|
||
for (var i : outLayers) {
|
||
names.add(layersNames.get(i - 1));
|
||
}
|
||
return names;
|
||
}
|
||
/**
|
||
* Returns the index of the maximum value in the array.
|
||
*/
|
||
private int argMax(double[] array) {
|
||
var best = 0;
|
||
var max = array[0];
|
||
for (var i = 1; i < array.length; i++) {
|
||
if (array[i] > max) {
|
||
max = array[i];
|
||
best = i;
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
}
|