Обзор проекта
функциональные точки
Пользователь загружает изображение, которое нужно обнаружить, во внешний интерфейс и нажимает кнопку, чтобы активировать обнаружение. Серверная часть завершает вывод алгоритма обнаружения цели для входящего изображения из внешнего интерфейса и возвращает изображение с обнаруженной ограничивающей рамкой во внешний интерфейс. Интерфейс отображает исходное изображение и результат после обнаружения.
визуализация
технический пункт
Фронтенд использует html+js+jquery, а бэкэнд использует SpringBoot+библиотеку глубокого обучения DJL, которая не задействует базу данных.
Фронтальную технологию я даже не считаю, поэтому много времени потратил на то, как передавать данные изображения во фронтенд и бэкенд, что здесь и записано.
Интерфейс загружает и передает изображения на сервер
Используйте тег input, чтобы пользователи могли загружать локальные изображения.
<input id="browse_file" type="file" accept="image/*"/>
Данные изображения инкапсулируются в форме FormData для передачи во внутреннюю службу.
var file=$('#browse_file')[0].files[0];
var formData=new FormData();
formData.append('file', file); //属性名称必须和后端接口@RequestParam标记的参数名一致
console.log("formData:", formData.get('file'));
$.ajax({
url : "http://localhost:8080/detectshow/formdata",
type : 'POST',
data : formData,
processData : false, //必须false才会避开jQuery对 formdata 的默认处理
contentType : false, //必须false才会自动加上正确的Content-Type
success : function(result) { //jquery请求返回的结果好像都是字符串类型
console.log("reponse result:", result);
},
error : function(result) {
console.log("reponse result:", result);
alert("Post Faile!");
}
});
Внутренний интерфейс использует MultipartFile для получения данных FormData, переданных внешним интерфейсом.@RequestParam("file")
Имя параметра, определяемое аннотацией, должно совпадать с именем свойства интерфейсной инкапсуляции FormData.formData.append('file', file)
Сохраняйте согласованность, иначе внешний интерфейс сообщит об ошибке при вызове внутреннего сервиса:Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present] cross_road
.
@ResponseBody
@RequestMapping(value="/detectshow/formdata", method = RequestMethod.POST)
public String detectShowImageOfFormData(@RequestParam("file") MultipartFile file) throws ModelException, TranslateException, IOException {
return cvService.objectDetectShowFormData(file);
}
Серверная часть загружает изображения на внешний дисплей
Поискав в интернете, я знаю, что есть как минимум три способа:
- Бэкэнд сохраняет изображение на сервере хранения файлов, возвращает URL-адрес изображения во внешний интерфейс, а внешний интерфейс может установить src=url тега img. Этот метод должен быть основным, но я не хочу создавать еще один сервер хранения, поэтому я не использовал этот метод.
- Серверная часть возвращает двоичные данные изображения во внешний интерфейс в виде массива байтов, а внешний интерфейс анализирует их для отображения изображения. Однако byte[], переданный из бэкенда, полученный jquery, действительно является массивом символов, и необходимо сначала передать массив символов в массив байтов, а затем разобрать его в изображение, что не так быстро, как кодировка base64 .
- Серверная часть возвращает кодировку изображения base64 во внешний интерфейс в виде строки, а внешний интерфейс анализирует ее для отображения изображения. Хотя существует вероятность того, что при использовании этого метода большие изображения могут быть обрезаны, выбор этого метода удобен.
$.ajax({
url : "http://localhost:8080/detectshow/formdata",
type : 'POST',
data : formData,
processData : false,
contentType : false,
success : function(result) { //jquery请求返回的结果好像都是字符串类型
console.log("reponse result:", result);
var src = 'data:image/png;base64,' + result; //直接把base64编码作为img标签的src属性值
$("#detected_img").attr('src', src);
$("#detected_img").css("width", "70%");
$("#detected_img").css("height", "70%");
},
error : function(result) {
console.log("reponse result:", result);
alert("Post Faile!");
}
});
Deep Java Library
Вы можете обратиться к предыдущему блогу.
Строительство проекта
среда разработки
Win10 + JDK 8 + SpringBoot 2.4.1 + jquery 1.10.2 + DJL 0.9.0
Структура проекта
Внешний интерфейс относительно прост, всего лишь простой html-файл для загрузки локальных изображений и отправки их в фон для обработки, принятия результатов обнаружения, возвращаемых фоном, и их отображения.
Серверная часть использует SpringBoot для создания веб-приложения, предоставляет служебный интерфейс, принимает изображения, переданные внешним интерфейсом, и возвращает результирующее изображение с ограничивающей рамкой обнаружения во внешний интерфейс в виде кодировки base64. после завершения обнаружения.
-
src/pom.xml
Зависимости конфигурации файла. -
src/main/java/[package]/SpringbootDlCvApplication.java
Запустите портал для приложения. -
src/main/java/[package]/controller/CVController.java
Определите открытый Restful API. -
src/main/java/[package]/service/CVService.java
Определите, какие службы используются для поддержки функций API. -
src/main/java/[package]/service/impl/CVServiceImpl.java
выполнитьCityService
определенный интерфейс. -
build/output/
Сохраните изображение результата теста.
титульная страница
Внешний интерфейс загружает локальное изображение с входным тегом и отправляет изображение на серверную часть в форме FormData в качестве тела POST-запроса. При этом результат обнаружения (кодировка картинки base64), возвращаемый фоном, принимается и отображается.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Object Detection Demo</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
</head>
<body>
<h4>Please chose an image to process object detection</h4>
<input id="browse_file" type="file" accept="image/*" onchange="showImg(this)"/>
<button id="detect_click" onclick="detectImg()">detect</button>
<div id="main" style="width:100%;">
<div id="left" style="width:50%;float:left;">
<p>origal image</p>
<img id="origal_img" src="" alt="" width="" height="">
</div>
<div id="right" style="width:50%;float:left;">
<p>detected image</p>
<img id="detected_img" src="" alt="" width="" height="">
</div>
</div>
<script>
function detectImg() {
console.log("object detection start...");
var file=$('#browse_file')[0].files[0];
var formData=new FormData();
formData.append('file', file); //属性名称必须和后端接口@RequestParam标记的参数名一致
console.log("formData:", formData.get('file'));
$.ajax({
url : "http://localhost:8080/detectshow/formdata",
type : 'POST',
data : formData,
processData : false, //必须false才会避开jQuery对 formdata 的默认处理
contentType : false, //必须false才会自动加上正确的Content-Type
success : function(result) { //jquery请求返回的结果好像都是字符串类型
console.log("reponse result:", result);
var src = 'data:image/png;base64,' + result;
$("#detected_img").attr('src', src);
$("#detected_img").css("width", "70%");
$("#detected_img").css("height", "70%");
},
error : function(result) {
console.log("reponse result:", result);
alert("Post Faile!");
}
});
console.log("object detection end!");
}
function showImg(obj) { //预览图片
var file=$(obj)[0].files[0]; //获取文件信息
var formData=new FormData();
formData.append('img',file); //只是前端预览, 属性名称可以随便定义
if(file) {
var reader=new FileReader(); //调用FileReader
reader.readAsDataURL(file); //将文件读取为 DataURL(base64)
reader.onload=function(evt){ //读取操作完成时触发。
$("#origal_img").attr('src', evt.target.result); //将img标签的src绑定为DataURL
$("#origal_img").css('width', "70%");
$("#origal_img").css('height', "70%");
};
}else {
alert("上传失败");
}
}
</script>
</body>
</html>
серверная служба
импортировать зависимости
В основном познакомьтесь с djl и связанными с ним зависимостями.
<!-->以BOM的方式统一管理依赖包的版本<-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>ai.djl</groupId>
<artifactId>bom</artifactId>
<version>0.9.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-->使用djl必须引入的依赖包<-->
<dependency>
<groupId>ai.djl</groupId>
<artifactId>api</artifactId>
</dependency>
<!-->使用不同的深度学习框架模型引入不同的依赖包, Apache MXNet engine implementation<-->
<dependency>
<groupId>ai.djl.mxnet</groupId>
<artifactId>mxnet-engine</artifactId>
</dependency>
<!-->使用不同的深度学习框架模型引入不同的依赖包, Apache MXNet native library<-->
<dependency>
<groupId>ai.djl.mxnet</groupId>
<artifactId>mxnet-native-auto</artifactId>
<scope>runtime</scope>
</dependency>
<!-->使用不同的深度学习框架模型引入不同的依赖包, A ModelZoo containing models exported from Apache MXNet<-->
<dependency>
<groupId>ai.djl.mxnet</groupId>
<artifactId>mxnet-model-zoo</artifactId>
</dependency>
</dependencies>
Определение и реализация интерфейса службы
-
src/main/java/[package]/service/CVService.java
Определите, какие службы могут поддерживать функции API.
package com.town.djl.cv.service;
import ai.djl.ModelException;
import ai.djl.translate.TranslateException;
import com.town.djl.cv.domain.DetectResultVO;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
public interface CVService {
/*
* 前端以FormData传入图片数据, 后台服务对该图片进行目标检测, 返回检测结果图片的base64编码
*/
public String objectDetectShowFormData(MultipartFile file) throws IOException, ModelException, TranslateException;
}
-
src/main/java/[package]/service/CVServiceImpl.java
Определите конкретную реализацию интерфейса службы.
//省略包名和import
@Service
public class CVServiceImpl implements CVService {
private static final String imgRoot = "src/test/resources/";
private static final String outRoot = "build/output";
private static final Logger logger = LoggerFactory.getLogger(CVServiceImpl.class);
/*对输入的Image进行目标检测,返回检测结果*/
public DetectedObjects predict(Image img, String imageName) throws IOException, ModelException, TranslateException {
String backbone;
if ("TensorFlow".equals(Engine.getInstance().getEngineName())) {
backbone = "mobilenet_v2";
} else {
backbone = "resnet50";
}
Criteria<Image, DetectedObjects> criteria =
Criteria.builder()
.optApplication(Application.CV.OBJECT_DETECTION)
.setTypes(Image.class, DetectedObjects.class)
.optFilter("backbone", backbone)
.optProgress(new ProgressBar())
.build();
try (ZooModel<Image, DetectedObjects> model = ModelZoo.loadModel(criteria)) {
try (Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
DetectedObjects detection = predictor.predict(img);
saveBoundingBoxImage(img, detection, imageName);
return detection;
}
}
}
/*将检测所得的bbox画在图上并保存的指定文件*/
private void saveBoundingBoxImage(Image img, DetectedObjects detection, String imageName)
throws IOException {
Path outputDir = Paths.get(outRoot);
Files.createDirectories(outputDir);
// Make image copy with alpha channel because original image was jpg
Image newImage = img.duplicate(Image.Type.TYPE_INT_ARGB);
newImage.drawBoundingBoxes(detection);
Path imagePath = outputDir.resolve(imageName + ".png");
// OpenJDK can't save jpg with alpha channel
newImage.save(Files.newOutputStream(imagePath), "png");
logger.info("Detected objects image has been saved in: {}", imagePath);
}
/*DJL提供的Image格式,从InputStream读取图片*/
public Image readImageByFormData(MultipartFile file) throws IOException {
if (file.isEmpty()) {
return null;
}
//通过输入流获取图片数据
InputStream inStream = file.getInputStream();
//得到图片的二进制数据,以二进制封装得到数据,具有通用性
Image img = ImageFactory.getInstance().fromInputStream(inStream);
//关闭输出流
inStream.close();
return img;
}
public String objectDetectShowFormData(MultipartFile file) throws IOException, ModelException, TranslateException {
String fileName = file.getOriginalFilename().split("\\.")[0]; //获取文件名
System.out.println(fileName);
Image img = readImageByFormData(file);
DetectedObjects detections = predict(img, fileName);
File imgFile = new File(outRoot + "/" + fileName + ".png");
InputStream inStream =new FileInputStream(imgFile);
byte imgBytes[] = new byte[(int) imgFile.length()]; //创建合适文件大小的数组
inStream.read(imgBytes); //读取文件里的内容到b[]数组
inStream.close();
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encodeBuffer(imgBytes);
}
}
Определение интерфейса Restful API
-
src/main/java/[package]/controller/CVController.java
Определите, какие службы могут поддерживать функции API.
//省略包名和import
@Controller
@RequestMapping("")
public class CVController {
@Autowired
private CVService cvService;
@CrossOrigin //允许跨域访问
@ResponseBody
@RequestMapping(value="/detectshow/formdata", method = RequestMethod.POST)
public String detectShowImageOfFormData(@RequestParam("file") MultipartFile file) throws ModelException, TranslateException, IOException {
return cvService.objectDetectShowFormData(file);
}
}
@RequestParam("file")
Имя параметра, определяемое аннотацией, должно совпадать с именем свойства интерфейсной инкапсуляции FormData.formData.append('file', file)
Сохраняйте согласованность, иначе внешний интерфейс сообщит об ошибке при вызове внутреннего сервиса:Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present] cross_road
.
Запись приложения
-
src/main/java/[package]/SpringbootDlCvApplication.java
Определите, какие службы могут поддерживать функции API.
package com.town.djl.cv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootDlCvApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDlCvApplication.class, args);
}
}
Побить пит-рекорд
Вопрос 1: Имя параметра не соответствует аннотации @RequestParam при вызове результата, и вызов сообщает об ошибке.
@RequestMapping(value="/image", method = RequestMethod.POST)
public @ResponseBody String detectImage(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename(); //获取文件名
System.out.println(fileName);
return "200";
}
Решение. При вызове интерфейса параметры должны соответствовать именам параметров, указанным в аннотации @RequestParam.
Вопрос 2: Фоновая служба выполняется успешно, также есть код состояния возврата, но Postman отображает исключение.
Код:
@RequestMapping(value="/image", method = RequestMethod.POST)
public String detectImage(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename(); //获取文件名
System.out.println(fileName);
return "200";
}
Результат звонка почтальона:
Решение: возвращаемый результат не определен в определении интерфейса, просто добавьте аннотацию @ResponseBody.
@RequestMapping(value="/image", method = RequestMethod.POST)
public @ResponseBody String detectImage(@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename(); //获取文件名
System.out.println(fileName);
return "200";
}