切換語言為:簡體

SpringBoot 整合 TensorFlow : 本地實現圖片合規性安全檢測

  • 爱糖宝
  • 2024-08-31
  • 2051
  • 0
  • 0

一、簡介

你是否還在為如何處理非法圖片而感到困惑?在涉及使用者檔案上傳的系統中,圖片內容的稽覈變得至關重要。不當圖片不僅影響使用者體驗,還可能帶來法律風險。依賴外部服務進行稽覈會帶來資料隱私問題和速度瓶頸。

爲了解決這些問題,我們引入了一個技術亮點:基於 TensorFlow 模型的本地圖片內容安全檢測,並整合在 SpringBoot 應用中。這一方案可以在本地環境直接稽覈圖片內容,無需外部 API,確保資料私密性,同時提升稽覈速度和系統穩定性。透過這種本地化的智慧稽覈,系統能夠更自主高效地處理非法圖片,為使用者提供更安全的體驗。

二、NSFW 介紹

因為專案中會使用到 NSFW的模型,所以這裏簡單介紹一下

1. 描述

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

2. 評判指標

NSFW 模型透過以下幾個分類指標來判斷圖片屬於哪一類內容。使用者可以設定對應的機率閾值來判定圖片是否符合某一類:

  • DRAWINGS: 卡通或漫畫圖片,這類圖片通常為手繪或數字繪圖風格。

  • HENTAI: 帶有情色成分的動畫或漫畫,包含成人內容但以動畫風格呈現。

  • NEUTRAL: 正常的、適合公開展示的影象,無不當內容。

  • PORN: 色情圖片,包含明顯的成人內容和性行為。

  • SEXY: 暗示性強的影象,雖然不完全屬於色情類別,但具有強烈的性暗示。

這些分類指標幫助系統自動化地對影象內容進行分類和過濾,從而有效地防止不適當內容的傳播。

三、功能演示

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

SpringBoot 整合 TensorFlow : 本地實現圖片合規性安全檢測

  • 當我們上傳一張具有違規行為的圖片

SpringBoot 整合 TensorFlow : 本地實現圖片合規性安全檢測

四、編碼實現

1. 引入依賴

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

2. 初始化NSFW模型

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

注意事項:

  • 在打包後 new ClassPathResource("").getFile().getAbsolutePath(); 的訪問會出現問題(文章末尾描述解決辦法)

/**
 * NSFW 模型
 *
 * @author : YiFei
 */
@Getter
@Component
public 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();
    }
}

如果需要輸出模型的詳細資訊,可以加上以下程式碼在 SavedModelBundle model = SavedModelBundle.load(modelPath, "serve");

//            以下是獲取模型 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 模型,並提高專案的開發效率。

工具類的具體實現讀者無需深入理解,只需瞭解其輸入輸出資訊以及如何使用即可。由於在之前的文章中工具類程式碼過多,影響了閱讀體驗,因此在這裏我貼出了使用到的工具類原始碼以及大致介紹。

  • NSFWAnalyzerUtils.java

    • Map<String, String> getNsfwPredictions(MultipartFile file):該方法接收一個 MultipartFile 物件,先透過 Image.read() 將圖片轉換為 BufferedImage,然後提取圖片的 RGB 值作為模型輸入張量,最後解析模型輸出張量並返回各個 NSFW 指標的機率。

    • boolean isNsfwFile(MultipartFile file):該方法與 getNsfwPredictions(MultipartFile file) 的處理過程類似,但增加了開發者設定的閾值判斷。當檢測到的非法指標機率達到或超過該閾值時,圖片將被判定為非法。

  • TensorflowUtil.java

    • static String getModelPath(String classPathResource) 將Resource下的save_model檔案轉換到臨時檔案中,返回臨時檔案絕對路徑。

4. 編寫 RESTful 介面

@RestController
@RequestMapping("nsfw")
@RequiredArgsConstructor
public 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);
        }
    }

}

五、解決模型載入時部署問題

在專案被打成 Jar 包後,使用 new ClassPathResource("XXX").getFile().getAbsolutePath(); 是無法訪問到資源的絕對路徑的。爲了解決這個問題,我們透過將 resource 中 save_model 目錄下的所有檔案轉存到臨時目錄,再將臨時目錄的路徑返回給模型進行載入。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.