java 违规图片、视频自动识别(色情、血腥、暴力)
最近工作上有需要,需要对用户上传的图片和视频做违规自动识别,网上当然有各种大厂的接口可以调用,但是由于项目的特殊性,不能使用外网,所以只有自己弄了。
查询资料,网上都是各种python的文章,也测试过一些,由于我对python不是很熟悉,各种环境依赖没整明白,考虑到后期还要上线到服务器,如果环境这么复杂,可能对现场实施人员不太友好。
后续找到有spring的包:open-nsfw-spring-boot-starter,提供了违规图片的识别。
我的方案目前是:
1、图片违规识别直接使用三方工具。
2、视频违规识别,首先按帧截图,保存为图片,再使用图片的识别方式。
这种方案缺点就是使用过程中对文件IO开销比较大,但是目前也找不到其他方案了,下面上代码。
1、maven引用工具包
<dependency> <groupId>com.ruibty.nsfw</groupId> <artifactId>open-nsfw-spring-boot-starter</artifactId> <version>1.0</version> </dependency>
2、视频文件抽帧处理,我这里用的是ffmpeg,可自行替换。我这里使用的是直接执行commond命令,因为ffmpeg不支持多线程,所以这里做了限制。
/** * 视频文件帧处理 * * @param filePath 帧文件保存地址 * @return */ public synchronized static String videoFrame(String filePath) { boolean exists = Files.exists(Paths.get(filePath)); Assert.isTrue(exists, "原文件不存在!"); Path path = Paths.get(filePath); // 文件名 String fileName = path.getFileName().toString(); int dotIndex = fileName.lastIndexOf('.'); String fileNameWithoutExtension = dotIndex == -1 ? fileName : fileName.substring(0, dotIndex); Path parentPath = path.getParent(); // 父级文件夹 String parentDirPath = parentPath == null ? "" : parentPath.toString(); // 使用文件名创建一个文件夹,准备存帧文件 String frameFilePath = parentDirPath + "/" + fileNameWithoutExtension + "_" + "video_frame"; File tsDir = new File(frameFilePath); if (!tsDir.exists()) { Assert.isTrue(tsDir.mkdirs(), "生成帧文件目录出错,生成目录:" + frameFilePath); } StringBuilder command = new StringBuilder(); command.append(LeenledaConfig.getFfmpegPath()); command.append(" ffmpeg "); command.append(" -i "); command.append(filePath); command.append(" -vf \"fps=1/3\" -qscale:v 2 "); command.append(frameFilePath); command.append("/video_frm_%05d.jpg"); try { if (!executeCommandBatch(command.toString())) { return null; } } catch (Exception ex) { ex.printStackTrace(); } return frameFilePath; }
3、执行cmmond工具类
import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /** * @author pengbenlei * @Description * @create 2024-03-20 10:05 */ @Slf4j public class CommandUtil { /** * 执行 linux/cmd 命令 * * @param command 命令 * @return */ public static boolean executeCommand(String command) { BufferedReader reader = null; InputStreamReader inputStreamReader = null; Process p; try { //判断是操作系统是linux还是windows String[] comds; if (System.getProperties().get("os.name").toString().toUpperCase().indexOf("WINDOWS") >= 0) { log.info("当前操作系统:windows"); comds = new String[]{"cmd", "/c", command}; } else { log.info("当前操作系统:linux"); comds = new String[]{"/bin/sh", "-c", command}; } // 开始执行命令 log.info("执行命令:" + command); p = Runtime.getRuntime().exec(comds); //开启线程监听(此处解决 waitFor() 阻塞/锁死 问题) new RunThread(p.getInputStream(), "INFO").start(); new RunThread(p.getErrorStream(), "ERROR").start(); int flag = p.waitFor(); return true; } catch (IOException e) { log.error("Proto2Html 执行命令IO异常!", e); return false; } catch (InterruptedException e) { log.error("Proto2Html 执行命令中断异常!", e); return false; } catch (Exception e) { log.error("Proto2Html 执行命令出现异常!", e); return false; } finally { IOUtils.closeQuietly(reader); IOUtils.closeQuietly(inputStreamReader); } } /** * 直接执行,获取执行内容返回 * * @param command * @return * @throws IOException * @throws InterruptedException */ public static String executeCommandGetReturn(String command) throws IOException, InterruptedException { System.out.println(command); Process process = Runtime.getRuntime().exec(command); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = null; StringBuilder builder = new StringBuilder(); while ((line = reader.readLine()) != null) { builder.append(line); } process.waitFor(); int exitCode = process.exitValue(); if (exitCode == 0) { return builder.toString(); } return null; } } /** * 监听线程 */ @Slf4j class RunThread extends Thread { InputStream is; String printType; RunThread(InputStream is, String printType) { this.is = is; this.printType = printType; } public void run() { try { InputStreamReader isr = new InputStreamReader(is, "GBK"); BufferedReader br = new BufferedReader(isr); String line = null; while ((line = br.readLine())!=null){ log.info(printType + ">" + line); } } catch (IOException ioe) { ioe.printStackTrace(); } } }
3、违规图片识别方法
/** * 图片违规处理 仅支持单线程调用 * * @param filePath 文件目录 */ public synchronized void violationIdentificationByDyFile(Long fileId, String filePath) { boolean flagViolation = false; Path path = Paths.get(filePath); byte[] imageBytes = null; try { imageBytes = Files.readAllBytes(path); } catch (IOException e) { e.printStackTrace(); } float prediction = nsfwService.getPrediction(imageBytes); if (prediction >= MAXPREDICTION) { flagViolation = true; } if (flagViolation) { // 违规文件 } else { // 文件未违规 } }
4、MAXPREDICTION是我定义的一个静态变量,prediction 是系数,一般来说,0.8以上的文件都属于违规了。
5、违规审核需要结合人工,因为有些情况下会误判定,不能全靠自动识别。
本文来自博客园,作者:Rolay,转载请注明原文链接:https://www.cnblogs.com/rolayblog/p/18079899