• Spring Boot集成tensorflow实现图片检测服务


    1.什么是tensorflow

    TensorFlow名字的由来就是张量(Tensor)在计算图(Computational Graph)里的流动(Flow),如图。它的基础就是前面介绍的基于计算图的自动微分,除了自动帮你求梯度之外,它也提供了各种常见的操作(op,也就是计算图的节点),常见的损失函数,优化算法。

    tensorflow

    • TensorFlow 是一个用于研究和生产的开放源代码机器学习库。TensorFlow 提供了各种 API,可供初学者和专家在桌面、移动、网络和云端环境下进行开发。
    • TensorFlow是采用数据流图(data flow graphs)来计算,所以首先我们得创建一个数据流流图,然后再将我们的数据(数据以张量(tensor)的形式存在)放在数据流图中计算. 节点(Nodes)在图中表示数学操作,图中的边(edges)则表示在节点间相互联系的多维数据数组, 即张量(tensor)。训练模型时tensor会不断的从数据流图中的一个节点flow到另一节点, 这就是TensorFlow名字的由来。 张量(Tensor):张量有多种. 零阶张量为 纯量或标量 (scalar) 也就是一个数值. 比如 [1],一阶张量为 向量 (vector), 比如 一维的 [1, 2, 3],二阶张量为 矩阵 (matrix), 比如 二维的 [[1, 2, 3],[4, 5, 6],[7, 8, 9]],以此类推, 还有 三阶 三维的 … 张量从流图的一端流动到另一端的计算过程。它生动形象地描述了复杂数据结构在人工神经网中的流动、传输、分析和处理模式。

    在机器学习中,数值通常由4种类型构成: (1)标量(scalar):即一个数值,它是计算的最小单元,如“1”或“3.2”等。 (2)向量(vector):由一些标量构成的一维数组,如[1, 3.2, 4.6]等。 (3)矩阵(matrix):是由标量构成的二维数组。 (4)张量(tensor):由多维(通常)数组构成的数据集合,可理解为高维矩阵。

    tensorflow的基本概念

    • 图:描述了计算过程,Tensorflow用图来表示计算过程
    • 张量:Tensorflow 使用tensor表示数据,每一个tensor是一个多维化的数组
    • 操作:图中的节点为op,一个op获得/输入0个或者多个Tensor,执行并计算,产生0个或多个Tensor
    • 会话:session tensorflow的运行需要再绘话里面运行

    tensorflow写代码流程

    • 定义变量占位符
    • 根据数学原理写方程
    • 定义损失函数cost
    • 定义优化梯度下降 GradientDescentOptimizer
    • session 进行训练,for循环
    • 保存saver

    2.环境准备

    整合步骤

    1. 模型构建:首先,我们需要在TensorFlow中定义并训练深度学习模型。这可能涉及选择合适的网络结构、优化器和损失函数等。
    2. 训练数据准备:接下来,我们需要准备用于训练和验证模型的数据。这可能包括数据清洗、标注和预处理等步骤。
    3. REST API设计:为了与TensorFlow模型进行交互,我们需要在SpringBoot中创建一个REST API。这可以使用SpringBoot的内置功能来实现,例如使用Spring MVC或Spring WebFlux。
    4. 模型部署:在模型训练完成后,我们需要将其部署到SpringBoot应用中。为此,我们可以使用TensorFlow的Java API将模型导出为ONNX或SavedModel格式,然后在SpringBoot应用中加载并使用。

    在整合过程中,有几个关键点需要注意。首先,防火墙设置可能会影响TensorFlow训练过程中的网络通信。确保你的防火墙允许TensorFlow访问其所需的网络资源,以免出现训练中断或模型性能下降的问题。其次,要关注版本兼容性。SpringBoot和TensorFlow都有各自的版本更新周期,确保在整合时使用兼容的版本可以避免很多不必要的麻烦。

    模型下载

    模型构建和模型训练这块设计到python代码,这里跳过,感兴趣的可以下载源代码自己训练模型,咱们直接下载训练好的模型

    下载好了,解压放在/resources/inception_v3目录下

    3.代码工程

    实验目的

    实现图片检测

    pom.xml

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>springboot-demo</artifactId>
    7. <groupId>com.et</groupId>
    8. <version>1.0-SNAPSHOT</version>
    9. </parent>
    10. <modelVersion>4.0.0</modelVersion>
    11. <artifactId>Tensorflow</artifactId>
    12. <properties>
    13. <maven.compiler.source>11</maven.compiler.source>
    14. <maven.compiler.target>11</maven.compiler.target>
    15. </properties>
    16. <dependencies>
    17. <dependency>
    18. <groupId>org.springframework.boot</groupId>
    19. <artifactId>spring-boot-starter-web</artifactId>
    20. </dependency>
    21. <dependency>
    22. <groupId>org.springframework.boot</groupId>
    23. <artifactId>spring-boot-autoconfigure</artifactId>
    24. </dependency>
    25. <dependency>
    26. <groupId>org.springframework.boot</groupId>
    27. <artifactId>spring-boot-starter-test</artifactId>
    28. <scope>test</scope>
    29. </dependency>
    30. <dependency>
    31. <groupId>org.tensorflow</groupId>
    32. <artifactId>tensorflow-core-platform</artifactId>
    33. <version>0.5.0</version>
    34. </dependency>
    35. <dependency>
    36. <groupId>org.projectlombok</groupId>
    37. <artifactId>lombok</artifactId>
    38. </dependency>
    39. <dependency>
    40. <groupId>jmimemagic</groupId>
    41. <artifactId>jmimemagic</artifactId>
    42. <version>0.1.2</version>
    43. </dependency>
    44. <dependency>
    45. <groupId>jakarta.platform</groupId>
    46. <artifactId>jakarta.jakartaee-api</artifactId>
    47. <version>9.0.0</version>
    48. </dependency>
    49. <dependency>
    50. <groupId>commons-io</groupId>
    51. <artifactId>commons-io</artifactId>
    52. <version>2.16.1</version>
    53. </dependency>
    54. <dependency>
    55. <groupId>org.springframework.restdocs</groupId>
    56. <artifactId>spring-restdocs-mockmvc</artifactId>
    57. <scope>test</scope>
    58. </dependency>
    59. </dependencies>
    60. </project>

    controller

    1. package com.et.tf.api;
    2. import java.io.IOException;
    3. import com.et.tf.service.ClassifyImageService;
    4. import net.sf.jmimemagic.Magic;
    5. import net.sf.jmimemagic.MagicMatch;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.web.bind.annotation.CrossOrigin;
    8. import org.springframework.web.bind.annotation.PostMapping;
    9. import org.springframework.web.bind.annotation.RequestMapping;
    10. import org.springframework.web.bind.annotation.RequestParam;
    11. import org.springframework.web.bind.annotation.RestController;
    12. import org.springframework.web.multipart.MultipartFile;
    13. @RestController
    14. @RequestMapping("/api")
    15. public class AppController {
    16. @Autowired
    17. ClassifyImageService classifyImageService;
    18. @PostMapping(value = "/classify")
    19. @CrossOrigin(origins = "*")
    20. public ClassifyImageService.LabelWithProbability classifyImage(@RequestParam MultipartFile file) throws IOException {
    21. checkImageContents(file);
    22. return classifyImageService.classifyImage(file.getBytes());
    23. }
    24. @RequestMapping(value = "/")
    25. public String index() {
    26. return "index";
    27. }
    28. private void checkImageContents(MultipartFile file) {
    29. MagicMatch match;
    30. try {
    31. match = Magic.getMagicMatch(file.getBytes());
    32. } catch (Exception e) {
    33. throw new RuntimeException(e);
    34. }
    35. String mimeType = match.getMimeType();
    36. if (!mimeType.startsWith("image")) {
    37. throw new IllegalArgumentException("Not an image type: " + mimeType);
    38. }
    39. }
    40. }

    service

    1. package com.et.tf.service;
    2. import jakarta.annotation.PreDestroy;
    3. import java.util.Arrays;
    4. import java.util.List;
    5. import lombok.AllArgsConstructor;
    6. import lombok.Data;
    7. import lombok.NoArgsConstructor;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.beans.factory.annotation.Value;
    10. import org.springframework.stereotype.Service;
    11. import org.tensorflow.Graph;
    12. import org.tensorflow.Output;
    13. import org.tensorflow.Session;
    14. import org.tensorflow.Tensor;
    15. import org.tensorflow.ndarray.NdArrays;
    16. import org.tensorflow.ndarray.Shape;
    17. import org.tensorflow.ndarray.buffer.FloatDataBuffer;
    18. import org.tensorflow.op.OpScope;
    19. import org.tensorflow.op.Scope;
    20. import org.tensorflow.proto.framework.DataType;
    21. import org.tensorflow.types.TFloat32;
    22. import org.tensorflow.types.TInt32;
    23. import org.tensorflow.types.TString;
    24. import org.tensorflow.types.family.TType;
    25. //Inspired from https://github.com/tensorflow/tensorflow/blob/master/tensorflow/java/src/main/java/org/tensorflow/examples/LabelImage.java
    26. @Service
    27. @Slf4j
    28. public class ClassifyImageService {
    29. private final Session session;
    30. private final List<String> labels;
    31. private final String outputLayer;
    32. private final int W;
    33. private final int H;
    34. private final float mean;
    35. private final float scale;
    36. public ClassifyImageService(
    37. Graph inceptionGraph, List<String> labels, @Value("${tf.outputLayer}") String outputLayer,
    38. @Value("${tf.image.width}") int imageW, @Value("${tf.image.height}") int imageH,
    39. @Value("${tf.image.mean}") float mean, @Value("${tf.image.scale}") float scale
    40. ) {
    41. this.labels = labels;
    42. this.outputLayer = outputLayer;
    43. this.H = imageH;
    44. this.W = imageW;
    45. this.mean = mean;
    46. this.scale = scale;
    47. this.session = new Session(inceptionGraph);
    48. }
    49. public LabelWithProbability classifyImage(byte[] imageBytes) {
    50. long start = System.currentTimeMillis();
    51. try (Tensor image = normalizedImageToTensor(imageBytes)) {
    52. float[] labelProbabilities = classifyImageProbabilities(image);
    53. int bestLabelIdx = maxIndex(labelProbabilities);
    54. LabelWithProbability labelWithProbability =
    55. new LabelWithProbability(labels.get(bestLabelIdx), labelProbabilities[bestLabelIdx] * 100f, System.currentTimeMillis() - start);
    56. log.debug(String.format(
    57. "Image classification [%s %.2f%%] took %d ms",
    58. labelWithProbability.getLabel(),
    59. labelWithProbability.getProbability(),
    60. labelWithProbability.getElapsed()
    61. )
    62. );
    63. return labelWithProbability;
    64. }
    65. }
    66. private float[] classifyImageProbabilities(Tensor image) {
    67. try (Tensor result = session.runner().feed("input", image).fetch(outputLayer).run().get(0)) {
    68. final Shape resultShape = result.shape();
    69. final long[] rShape = resultShape.asArray();
    70. if (resultShape.numDimensions() != 2 || rShape[0] != 1) {
    71. throw new RuntimeException(
    72. String.format(
    73. "Expected model to produce a [1 N] shaped tensor where N is the number of labels, instead it produced one with shape %s",
    74. Arrays.toString(rShape)
    75. ));
    76. }
    77. int nlabels = (int) rShape[1];
    78. FloatDataBuffer resultFloatBuffer = result.asRawTensor().data().asFloats();
    79. float[] dst = new float[nlabels];
    80. resultFloatBuffer.read(dst);
    81. return dst;
    82. }
    83. }
    84. private int maxIndex(float[] probabilities) {
    85. int best = 0;
    86. for (int i = 1; i < probabilities.length; ++i) {
    87. if (probabilities[i] > probabilities[best]) {
    88. best = i;
    89. }
    90. }
    91. return best;
    92. }
    93. private Tensor normalizedImageToTensor(byte[] imageBytes) {
    94. try (Graph g = new Graph();
    95. TInt32 batchTensor = TInt32.scalarOf(0);
    96. TInt32 sizeTensor = TInt32.vectorOf(H, W);
    97. TFloat32 meanTensor = TFloat32.scalarOf(mean);
    98. TFloat32 scaleTensor = TFloat32.scalarOf(scale);
    99. ) {
    100. GraphBuilder b = new GraphBuilder(g);
    101. //Tutorial python here: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/label_image
    102. // Some constants specific to the pre-trained model at:
    103. // https://storage.googleapis.com/download.tensorflow.org/models/inception_v3_2016_08_28_frozen.pb.tar.gz
    104. //
    105. // - The model was trained with images scaled to 299x299 pixels.
    106. // - The colors, represented as R, G, B in 1-byte each were converted to
    107. // float using (value - Mean)/Scale.
    108. // Since the graph is being constructed once per execution here, we can use a constant for the
    109. // input image. If the graph were to be re-used for multiple input images, a placeholder would
    110. // have been more appropriate.
    111. final Output input = b.constant("input", TString.tensorOfBytes(NdArrays.scalarOfObject(imageBytes)));
    112. final Output output =
    113. b.div(
    114. b.sub(
    115. b.resizeBilinear(
    116. b.expandDims(
    117. b.cast(b.decodeJpeg(input, 3), DataType.DT_FLOAT),
    118. b.constant("make_batch", batchTensor)
    119. ),
    120. b.constant("size", sizeTensor)
    121. ),
    122. b.constant("mean", meanTensor)
    123. ),
    124. b.constant("scale", scaleTensor)
    125. );
    126. try (Session s = new Session(g)) {
    127. return s.runner().fetch(output.op().name()).run().get(0);
    128. }
    129. }
    130. }
    131. static class GraphBuilder {
    132. final Scope scope;
    133. GraphBuilder(Graph g) {
    134. this.g = g;
    135. this.scope = new OpScope(g);
    136. }
    137. Output div(Output x, Output y) {
    138. return binaryOp("Div", x, y);
    139. }
    140. Output sub(Output x, Output y) {
    141. return binaryOp("Sub", x, y);
    142. }
    143. Output resizeBilinear(Output images, Output size) {
    144. return binaryOp("ResizeBilinear", images, size);
    145. }
    146. Output expandDims(Output input, Output dim) {
    147. return binaryOp("ExpandDims", input, dim);
    148. }
    149. Output cast(Output value, DataType dtype) {
    150. return g.opBuilder("Cast", "Cast", scope).addInput(value).setAttr("DstT", dtype).build().output(0);
    151. }
    152. Output decodeJpeg(Output contents, long channels) {
    153. return g.opBuilder("DecodeJpeg", "DecodeJpeg", scope)
    154. .addInput(contents)
    155. .setAttr("channels", channels)
    156. .build()
    157. .output(0);
    158. }
    159. Output<? extends TType> constant(String name, Tensor t) {
    160. return g.opBuilder("Const", name, scope)
    161. .setAttr("dtype", t.dataType())
    162. .setAttr("value", t)
    163. .build()
    164. .output(0);
    165. }
    166. private Output binaryOp(String type, Output in1, Output in2) {
    167. return g.opBuilder(type, type, scope).addInput(in1).addInput(in2).build().output(0);
    168. }
    169. private final Graph g;
    170. }
    171. @PreDestroy
    172. public void close() {
    173. session.close();
    174. }
    175. @Data
    176. @NoArgsConstructor
    177. @AllArgsConstructor
    178. public static class LabelWithProbability {
    179. private String label;
    180. private float probability;
    181. private long elapsed;
    182. }
    183. }

    application.yaml

    1. tf:
    2. frozenModelPath: inception-v3/inception_v3_2016_08_28_frozen.pb
    3. labelsPath: inception-v3/imagenet_slim_labels.txt
    4. outputLayer: InceptionV3/Predictions/Reshape_1
    5. image:
    6. width: 299
    7. height: 299
    8. mean: 0
    9. scale: 255
    10. logging.level.net.sf.jmimemagic: WARN
    11. spring:
    12. servlet:
    13. multipart:
    14. max-file-size: 5MB

    Application.java

    1. package com.et.tf;
    2. import java.io.IOException;
    3. import java.nio.charset.StandardCharsets;
    4. import java.util.List;
    5. import java.util.stream.Collectors;
    6. import lombok.extern.slf4j.Slf4j;
    7. import org.apache.commons.io.IOUtils;
    8. import org.springframework.beans.factory.annotation.Value;
    9. import org.springframework.boot.SpringApplication;
    10. import org.springframework.boot.autoconfigure.SpringBootApplication;
    11. import org.springframework.context.annotation.Bean;
    12. import org.springframework.core.io.ClassPathResource;
    13. import org.springframework.core.io.FileSystemResource;
    14. import org.springframework.core.io.Resource;
    15. import org.tensorflow.Graph;
    16. import org.tensorflow.proto.framework.GraphDef;
    17. @SpringBootApplication
    18. @Slf4j
    19. public class Application {
    20. public static void main(String[] args) {
    21. SpringApplication.run(Application.class, args);
    22. }
    23. @Bean
    24. public Graph tfModelGraph(@Value("${tf.frozenModelPath}") String tfFrozenModelPath) throws IOException {
    25. Resource graphResource = getResource(tfFrozenModelPath);
    26. Graph graph = new Graph();
    27. graph.importGraphDef(GraphDef.parseFrom(graphResource.getInputStream()));
    28. log.info("Loaded Tensorflow model");
    29. return graph;
    30. }
    31. private Resource getResource(@Value("${tf.frozenModelPath}") String tfFrozenModelPath) {
    32. Resource graphResource = new FileSystemResource(tfFrozenModelPath);
    33. if (!graphResource.exists()) {
    34. graphResource = new ClassPathResource(tfFrozenModelPath);
    35. }
    36. if (!graphResource.exists()) {
    37. throw new IllegalArgumentException(String.format("File %s does not exist", tfFrozenModelPath));
    38. }
    39. return graphResource;
    40. }
    41. @Bean
    42. public List tfModelLabels(@Value("${tf.labelsPath}") String labelsPath) throws IOException {
    43. Resource labelsRes = getResource(labelsPath);
    44. log.info("Loaded model labels");
    45. return IOUtils.readLines(labelsRes.getInputStream(), StandardCharsets.UTF_8).stream()
    46. .map(label -> label.substring(label.contains(":") ? label.indexOf(":") + 1 : 0)).collect(Collectors.toList());
    47. }
    48. }

    以上只是一些关键代码,所有代码请参见下面代码仓库

    代码仓库

    4.测试

    启动 Spring Boot应用程序

    测试图片分类

    访问http://127.0.0.1:8080/,上传一张图片,点击分类

     

    5.引用

     

  • 相关阅读:
    利用API数据接口进行市场调研的详细指南
    抽取泛微和建云的销售合同定时任务(要求记录翻译不成功的字段)
    单例模式、工厂模式 c++关键字 static
    详解数据管理、数据治理、数据资产管理
    java计算机毕业设计旅游管理系统源码+mysql数据库+系统+lw文档+部署
    冰冰学习笔记:Linux下的权限理解
    rust的排序
    PostgreSQL(一) 编译安装运行
    【算法】十一月阳光下的阴影面积
    seq2seq与引入注意力机制的seq2seq
  • 原文地址:https://blog.csdn.net/dot_life/article/details/139818407