當前位置: 華文世界 > 科技

SpringBoot整合TensorFlow對圖片內容進行安全檢測 : 全流程指南

2024-08-29科技

一、TensorFlow介紹

1. 什麽是TensorFlow

TensorFlow 是由Google開發的開源機器學習框架,廣泛用於構建和訓練神經網絡模型。它提供了靈活的工具集,支持各種機器學習和深度學習任務,包括影像分類、自然語言處理和時間序列分析。TensorFlow的強大之處在於它的跨平台特性,支持從流動通訊器材到大型分布式系統的部署,同時有豐富的API和社區支持,適合從初學者到專家的不同需求。

2. TensorFlow的套用場景

  1. 影像辨識與分類 :TensorFlow最常見的套用之一是影像辨識和分類。例如,TensorFlow被用於自動化檢測圖片中的物體,如汽車、人、動植物等。這種技術廣泛套用於自動駕駛、智能監控和醫療影像分析等領域。
  2. 自然語言處理(NLP) :TensorFlow支持構建復雜的自然語言處理模型,如語音辨識、機器轉譯和情感分析。透過使用TensorFlow訓練的模型,應用程式可以實作從文本中提取資訊、生成自然語言回復、甚至進行跨語言的即時轉譯。
  3. 推薦系統 :許多線上平台使用TensorFlow來構建個人化推薦系統。例如,電商平台透過分析使用者的瀏覽歷史和購買行為,利用TensorFlow的深度學習模型生成個人化的商品推薦,從而提升使用者體驗和銷售額。
  4. 時間序列預測 :TensorFlow在金融、氣象和交通領域用於時間序列預測。透過歷史數據的學習,模型可以預測未來的趨勢或事件,如股票價格走勢、天氣變化或交通流量預測。
  5. 生成對抗網絡(GANs) :TensorFlow支持開發和訓練生成對抗網絡,用於生成逼真的影像、影片或音訊。例如,GANs可以被用於生成虛擬人物形象、創作藝術作品或增強現實內容。
  6. 醫療診斷 :在醫療領域,TensorFlow被用來開發診斷工具。例如,透過分析醫學影像(如X光片或MRI),TensorFlow模型可以輔助醫生早期發現病變或其他健康問題,提升診斷效率和準確性。

二、NSFW介紹

1. 什麽是NSFW

NSFW (Not Safe For Work)模型是一類專門用於檢測不適合在公共場所或工作環境中展示的內容的模型,通常指包含成人內容、暴力或其他不宜公開展示的圖片或影片。NSFW模型透過訓練深度學習演算法,能夠自動辨識這些不適合公開的內容,廣泛套用於社交媒體平台、內容稽核系統以及其他需要過濾敏感內容的套用場景。

三、具體實作

看完了 TensorFlow 和 NSFW 的介紹,下面讓我們來快速實作一下吧( 工具類和初始化模型不懂也沒關系復制貼上即可,只需要了解大致在做什麽,方法返回結果是什麽,就可以進行最簡單的使用

1. 引入依賴

<dependency> <groupId>org.tensorflow</groupId> <artifactId>tensorflow</artifactId></dependency>

2. 初始化NSFW模型

NSFW 模型可以透過 GitHub 獲取,推薦使用 nsfwjs[1] 專案中的模型。可以使用 Python 將模型轉換為 TensorFlow 的 saved_model 格式,然後在 SpringBoot 套用中進行載入。當然,也可以直接找到已轉換好的 NSFW saved_model 格式的模型進行載入。

在使用 TensorFlow API 後,載入 NSFW 模型變得非常簡單。我們將模型存放在專案的 \resources\tensorflow\saved_model\nsfw 目錄中,因此在載入時,只需將該目錄的絕對路徑傳入即可輕松完成模型的初始化 , 然後透過TensorFlow的session傳入對應張量就可以獲取到nsfw分類比例。

/** * NSFW 模型 * * @author : YiFei */@Getter@Componentpublic class NSFWModelService { // 提供方法來獲取 TensorFlow Session private Session session; @PostConstruct public void init() { // 載入 TensorFlow 模型 try { String modelAbsolutePath = new classPathResource("tensorflow/saved_model/nsfw").getFile().getAbsolutePath(); SavedModelBundle model = SavedModelBundle.load(modelPath, "serve"); this.session = model.session(); } catch (IOException e) { throw new RuntimeException(e); } } /** * 在銷毀 Bean 時關閉 TensorFlow Session */ @PreDestroy public void closeSession() { this.session.close(); }}

完成上述操作,我們即可在本地載入模型,如果需要部署還會出現一些細節上的問題,我們將在最後進行解析。如果需要打印載入模型的詳細資訊,可以加上以下程式碼

// 以下是獲取模型 Inputs 數據格式 、輸入張量名 , output 數據格式 、輸出張量名// MetaGraphDef metaGraphDef = MetaGraphDef.parseFrom(model.metaGraphDef());// Map<String, SignatureDef> signatureDefMap = metaGraphDef.getSignatureDefMap();//// for (Map.Entry<String, SignatureDef> entry : signatureDefMap.entrySet()) {// System.out.println("SignatureDef key: " + entry.getKey());//// SignatureDef signatureDef = entry.getValue();// System.out.println("Inputs:");// for (Map.Entry<String, TensorInfo> inputEntry : signatureDef.getInputsMap().entrySet()) {// String inputKey = inputEntry.getKey();// TensorInfo inputTensorInfo = inputEntry.getValue();//// // 打印輸入張量的名稱// System.out.println(" Key: " + inputKey);// System.out.println(" Name: " + inputTensorInfo.getName());//// // 打印輸入張量的形狀// if (inputTensorInfo.hasTensorShape()) {// TensorShapeProto tensorShape = inputTensorInfo.getTensorShape();// System.out.println(" Shape: " + tensorShape);// }//// // 打印輸入張量的數據類別// System.out.println(" Data Type: " + inputTensorInfo.getDtype());// }//// System.out.println("Outputs:");// for (Map.Entry<String, TensorInfo> outputEntry : signatureDef.getOutputsMap().entrySet()) {// String outputKey = outputEntry.getKey();// TensorInfo outputTensorInfo = outputEntry.getValue();//// // 打印輸出張量的名稱// System.out.println(" Key: " + outputKey);// System.out.println(" Name: " + outputTensorInfo.getName());//// // 打印輸出張量的形狀// if (outputTensorInfo.hasTensorShape()) {// TensorShapeProto tensorShape = outputTensorInfo.getTensorShape();// System.out.println(" Shape: " + tensorShape.toString());// }//// // 打印輸出張量的數據類別// System.out.println(" Data Type: " + outputTensorInfo.getDtype());// }// }

3. 編寫工具類

盡管 TensorFlow API 已經相對簡潔,但在實際使用中仍可能顯得繁瑣。為了解決這一問題,我們可以封裝一個工具類,使介面更加友好,讓開發者只需一行程式碼即可完成圖片內容安全的校驗,而無需編寫大量冗余的程式碼。透過這個工具類,您可以更輕松地整合 NSFW 模型,並提高專案的開發效率。

  • 解釋NSFW分類任務,您可以設定對應概率閾值,來判斷檔是否違規
  • DRAWINGS : 卡通或漫畫圖片
  • HENTAI : 帶有情色成分的動畫或漫畫
  • NEUTRAL : 正常的、適合公開展示的影像
  • PORN : 色情圖片
  • SEXY : 暗示性強的影像,但不完全是色情
  • 這個工具類 NSFWAnalyzerUtils 主要用於對上傳的圖檔進行 NSFW(Not Safe For Work)內容檢測。它提供了一些實用的方法,簡述如下:

    1. NSFW 內容預測
    2. 方法 getNsfwPredictions 透過呼叫預訓練的 NSFW 模型,返回一個擁有 NSFW 類別及其對應概率的對映。影像會被處理並轉換為模型可以理解的格式,預測結果會被格式化為百分比字串並儲存在 Map 中。
    3. 檔安全性判斷
    4. 方法 isNsfwFile 提供了基於預設或自訂閾值判斷上傳檔是否包含不安全內容的功能。該方法呼叫模型進行推理,並根據輸出結果和設定的閾值判斷檔是否安全。
    5. 影像處理
    6. 工具類內部還包括影像的預處理步驟,如調整影像大小、建立適用於 TensorFlow 的張量輸入格式,以及將影像的像素值歸一化以便模型推理。

    整體上,這個工具類簡化了 NSFW 模型的載入和呼叫流程,封裝了影像預處理和安全性判斷的邏輯,方便在套用中整合 NSFW 內容檢測功能。

    /** * nsfw 檔校驗工具類 * * @author : YiFei */@Slf4j@Component@RequiredArgsConstructorpublic class NSFWAnalyzerUtils { public static final float NSFWThreshold = 0.28f; public static final String PERCENTAGE_FORMAT = "#.###%"; // 格式化為百分比,保留三位小數 private final NSFWModelService nsfwModelService; /** * 對上傳的檔進行 NSFW 校驗,返回按概率排序的 NSFW 類別及其概率。 * * @param file 上傳的檔(MultipartFile) * @return 按概率排序的 NSFW 類別及其概率的 Map */ @SneakyThrows public Map<String, String> getNsfwPredictions(MultipartFile file) { // 使用模型從檔中提取 NSFW 預測結果,這裏假設 extractNSFWModelPredictions 返回一個 float[][] output float[][] output = extractNSFWModelPredictions(file); // NSFW 類別 ( "塗鴉", "色情動漫", "中性", "色情", "性感" ) String[] nsfwTypes = {"Drawing", "Hentai", "Neutral", "Porn", "Sexy"}; // 建立一個 Map 用於儲存 NSFW 類別及其概率 Map<String, String> nsfwProbabilities = new LinkedHashMap<>(); // 將模型輸出的概率陣列中的值按順序放入 Map 中,並格式化為百分比字串 DecimalFormat df = new DecimalFormat(PERCENTAGE_FORMAT); for (int i = 0; i < output[0].length && i < nsfwTypes.length; i++) { // 格式化為百分比字串 String formattedProbability = df.format(output[0][i]); nsfwProbabilities.put(nsfwTypes[i], formattedProbability); } return nsfwProbabilities; } /** * 對上傳的檔進行 NSFW 校驗,返回檔是否不安全的判斷結果。 * 使用預設的 NSFW 閾值進行判斷。 * * @param file 上傳的檔(MultipartFile) * @return 如果檔被判斷為不安全,則返回 true;否則返回 false */ @SneakyThrows public boolean isNsfwFile(MultipartFile file) { // 使用模型從檔中提取 NSFW 預測結果 float[][] output = extractNSFWModelPredictions(file); // 使用預設的 NSFW 閾值判斷檔是否不安全 return isUnsafe(output, NSFWThreshold); } /** * 對上傳的檔進行 NSFW 校驗,返回檔是否不安全的判斷結果。 * * @param file 上傳的檔(MultipartFile) * @param NSFWThreshold 判斷檔不安全的閾值 * @return 如果檔被判斷為不安全,則返回 true;否則返回 false */ @SneakyThrows public boolean isNsfwFile(MultipartFile file, float NSFWThreshold) { // 使用模型從檔中提取 NSFW 預測結果 float[][] output = extractNSFWModelPredictions(file); // 使用指定的 NSFW 閾值判斷檔是否不安全 return isUnsafe(output, NSFWThreshold); } /** * 從上傳的檔中獲取浮點數陣列。 * * @param file 上傳的檔(MultipartFile) * @return 包含模型推理結果的浮點數陣列,形狀為 [1, 5] * @throws IOException 如果讀取檔或處理影像時發生錯誤 */ private float[][] extractNSFWModelPredictions(MultipartFile file) throws IOException { // 讀取上傳的圖檔並進行處理 BufferedImage image = ImageIO.read(file.getInputStream()); if (image == null) { throw new IOException("Failed to read image from file. / 無法讀取檔 :" + file.getOriginalFilename()); } // 將影像調整大小為模型期望的尺寸 BufferedImage resizedImage = resizeImage(image, 224, 224); // 建立輸入張量 Tensor<?> inputTensor = createImageTensor(resizedImage, resizedImage.getWidth(), resizedImage.getHeight()); // 執行模型推理並獲取結果 Tensor<?> result = nsfwModelService.getSession() .runner() .feed("serving_default_input:0", inputTensor) // 使用正確的輸入張量名稱 .fetch("StatefulPartitionedCall:0") // 使用正確的輸出張量名稱 .run() .get(0); // 處理模型輸出結果 float[][] output = new float[1][5]; // 根據模型的輸出格式調整陣列大小 result.copyTo(output); return output; } /** * 根據給定的 BufferedImage 建立對應的 TensorFlow 影像 Tensor。 * 影像將被轉換為指定的尺寸,並將像素值歸一化到 [0, 1] 範圍內作為 Tensor 數據。 * * @param image 要轉換為 Tensor 的 BufferedImage 物件 * @param width 目標影像寬度 * @param height 目標影像高度 * @return 表示影像的 TensorFlow Tensor 物件 */ private Tensor<?> createImageTensor(BufferedImage image, int width, int height) { int channels = 3; // 影像通道數為 RGB // 建立用於儲存影像像質數據的一維陣列 float[] tensorData = new float[height * width * channels]; // 遍歷影像的每個像素,並將 RGB 值歸一化後儲存到 tensorData 中 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int rgb = image.getRGB(x, y); // 提取並歸一化每個像素的 RGB 分量,儲存到 tensorData 中 tensorData[(y * width + x) * channels] = ((rgb >> 16) & 0xFF) / 255.0f; // 紅色通道 tensorData[(y * width + x) * channels + 1] = ((rgb >> 8) & 0xFF) / 255.0f; // 綠色通道 tensorData[(y * width + x) * channels + 2] = (rgb & 0xFF) / 255.0f; // 藍色通道 } } // 建立 TensorFlow Tensor 的形狀(Shape),即 [batch_size, height, width, channels] long[] shape = {1, height, width, channels}; // 使用歸一化後的像質數據建立 TensorFlow Tensor 物件 return Tensor.create(shape, FloatBuffer.wrap(tensorData)); } /** * 調整給定的 BufferedImage 到指定的寬度和高度。 * 使用 Image.SCALE_SMOOTH 縮放演算法以得到更平滑的輸出影像。 * * @param originalImage 要調整大小的原始 BufferedImage 物件 * @param width 目標影像寬度 * @param height 目標影像高度 * @return 調整大小後的 BufferedImage 物件 */ private BufferedImage resizeImage(BufferedImage originalImage, int width, int height) { // 使用指定的寬度和高度縮放原始影像 Image resultingImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); // 建立新的 BufferedImage 作為輸出影像 BufferedImage outputImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 獲取輸出影像的繪圖上下文 Graphics2D g2d = outputImage.createGraphics(); // 在輸出影像上繪制縮放後的影像 g2d.drawImage(resultingImage, 0, 0, null); // 釋放繪圖上下文資源 g2d.dispose(); // 返回呼整大小後的 BufferedImage 物件 return outputImage; } /** * 判斷檔是否不安全。 * <p> * 0 : Drawing -> safe for work drawings (including anime) 安全 * 1 : Hentai -> hentai and pornographic drawings 危險 * 2 : Neutral -> safe for work neutral images 安全 * 3 : Porn -> pornographic images, sexual acts 危險 * 4 : Sexy -> sexually explicit images, not pornography 危險 * * @param output 模型輸出結果的浮點數陣列,形狀為 [1, 5] * @param threshold 判定不安全的閾值 * @return 如果檔被認為是不安全的,返回 true;否則返回 false */ private boolean isUnsafe(float[][] output, float threshold) { // 模型輸出的類別索引: final int DRAWING = 0; final int HENTAI = 1; final int NEUTRAL = 2; final int PORN = 3; final int SEXY = 4; // 安全類別的索引集合 Set<Integer> safeCategories = Set.of(DRAWING, NEUTRAL); // 遍歷模型輸出結果 for (int i = 0; i < output[0].length; i++) { // 如果當前類別不是安全類別,並且其概率超過閾值,則判定為不安全 if (!safeCategories.contains(i) && output[0][i] > threshold) { return true; } } // 如果所有類別的概率都低於閾值,或都是安全類別,則判定為安全 return false; }}

    4. 提供對應介面

    其中使用工具類的 getNsfwPredictions 方法, 他會返回影像的預測結果其中包含Drawing、Hentai、Neutral、Porn、Sexy的對應概率

    @RestController@RequestMapping("nsfw")@RequiredArgsConstructorpublic class NsfwController { private final NSFWAnalyzerUtils nsfwAnalyzerUtils; @Operation(summary = "圖片檢測") @PreventDuplicateSubmit @PostMapping("/check") public Result<Map<String, String>> nsfwCheck(MultipartFile file) { try { return Result.success(nsfwAnalyzerUtils.getNsfwPredictions(file)); } catch (Exception e) { throw new ServiceException(ResultCode.FILE_ANALYZER_ERROR); } }}

    5. 測試

  • 當我們上傳一張正常的圖片
  • image.png

  • 當我們上傳一張好看的圖片(具體內容靠大家想象)
  • image.png

    註意 : 判斷結果與模型有關

    6. 解決部署問題

    在開發階段,專案可以直接存取 resources 目錄下的檔,但在打包為 JAR 後,資原始檔會嵌入到 JAR 中,無法作為檔案系統中的路徑直接存取。例如,以下程式碼在打包為 JAR 後會導致存取失敗:

    String modelAbsolutePath = new classPathResource("tensorflow/saved_model/nsfw").getFile().getAbsolutePath();

  • 解決方案:
  • 將專案部署在伺服器的指定位置
  • 生成臨時檔後再載入模型
  • 我采用了生成臨時檔的方法,即將 resources 目錄下的字節流檔復制到臨時目錄,並返回臨時目錄的路徑以載入模型。程式碼邏輯很簡單:在 Temp 目錄下建立檔,將資源目錄中的檔拷貝到臨時目錄,最後返回臨時檔的路徑用於模型載入。

    public static String getModelPath(String classPathResource) throws IOException { File tempModelDir = Files.createTempDirectory(TEMP_TENSORFLOW_MODEL_PATH).toFile(); Path tempModelDirPath = tempModelDir.toPath(); copyTempModel("", classPathResource, tempModelDirPath); copyTempModel("assets", classPathResource, tempModelDirPath); copyTempModel("variables", classPathResource, tempModelDirPath); log.info(" classPathResource: {} , ===> 臨時檔儲存在 {} ", classPathResource, tempModelDir.getAbsolutePath()); return tempModelDir.getAbsolutePath();}

    更詳細的程式碼可以參考 TensorFlowUtil[2]

    四、源碼

    源碼地址[3] | 線上演示[4] | 覺得不錯可以給個start

    前端源碼位置 : yf/ yf-vue-admin / src / views / demo / nsfw[5]

    後端源碼位置 :

    yf/ yf-boot-admin / yf-integration / yf-file[6]

    yf/ yf-boot-admin / yf-shared / src / main / java / com / yf / utils[7]

    註意事項 :

    平台一人一號,賬號可以透過郵箱、第三方平台自動註冊。使用者名稱密碼方式登入請聯系管理員手動添加、手機號不可用。(敏感數據以做資訊脫敏)

    線上聊天功能(訊息已做臟詞過濾,群發、系統、AI訊息不會被平台記錄)

    歡迎大家提出意見,歡迎暢聊與專案相關問題

    原文:https://juejin.cn/post/7408062004844625960

    作者:翼飛

    Reference

    [1] https://github.com/infinitered/nsfwjs: https://github.com/infinitered/nsfwjs

    [2] https://gitee.com/fateyifei/yf/blob/master/yf-boot-admin/yf-shared/src/main/java/com/yf/utils/TensorflowUtil.java: https://gitee.com/fateyifei/yf/blob/master/yf-boot-admin/yf-shared/src/main/java/com/yf/utils/TensorflowUtil.java

    [3] https://gitee.com/fateyifei/yf: https://gitee.com/fateyifei/yf

    [4] http://110.41.173.220/yf-vue-admin/login: http://110.41.173.220/yf-vue-admin/login

    [5] https://gitee.com/fateyifei/yf/tree/master/yf-vue-admin/src/views/demo/nsfw: https://gitee.com/fateyifei/yf/tree/master/yf-vue-admin/src/views/demo/nsfw

    [6] https://gitee.com/fateyifei/yf/tree/master/yf-boot-admin/yf-integration/yf-file: https://gitee.com/fateyifei/yf/tree/master/yf-boot-admin/yf-integration/yf-file

    [7] https://gitee.com/fateyifei/yf/tree/master/yf-boot-admin/yf-shared/src/main/java/com/yf/utils: https://gitee.com/fateyifei/yf/tree/master/yf-boot-admin/yf-shared/src/main/java/com/yf/utils