内容安全:使用开源框架Caffe实现上传图片进行敏感内容识别

上传图片进行敏感内容识别

预览效果

preview-html

环境准备

  • Ubuntu 16.04
  • python 2.7.12
  • caffe 1.0.0

安装调试环境:

sudo apt-get update
sudo apt-get install -y --no-install-recommends build-essential cmake git wget libatlas-base-dev libboost-all-dev libgflags-dev
sudo apt-get install -y --no-install-recommends libgoogle-glog-dev libhdf5-serial-dev libleveldb-dev liblmdb-dev libopencv-dev
sudo apt-get install -y --no-install-recommends libprotobuf-dev libsnappy-dev protobuf-compiler python-dev python-numpy python-pip python-setuptools python-scipy 
sudo apt-get install -y --no-install-recommends libprotobuf-dev libsnappy-dev protobuf-compiler python-dev python-numpy python-pip python-setuptools python-scipy 
# /var/lib/apt/lists/ 这个目录中存储的是 Ubuntu 或其他基于 Debian 的 Linux 系统在执行 apt-get update 命令时从软件源获取的包列表信息。
rm -rf /var/lib/apt/lists/*

拉取 caffe 源码, 并编译好

sudo mkdir -p /opt/caffe && cd /opt/caffe
sudo git clone -b 1.0 --depth 1 https://github.com/BVLC/caffe.git . 
python -m pip install --upgrade pip==20.3.4
cd python 
for req in $(cat requirements.txt) pydot; do pip install $req; done
cd ..
mkdir build
cd build 
cmake -DCPU_ONLY=1 ..
make -j"$(nproc)"

测试是否正确编译

make runtest
# 测试输出示例, 列举了部分输出
Built target caffeproto
Built target caffe
Built target gtest
Built target test.testbin
Note: Google Test filter = -*GPU*
Note: Randomizing tests' orders with a seed of 5941 .
[==========] Running 1162 tests from 152 test cases.
[----------] Global test environment set-up.
[----------] 6 tests from FlattenLayerTest/1, where TypeParam = caffe::CPUDevice<double>
[ RUN      ] FlattenLayerTest/1.TestForward
[       OK ] FlattenLayerTest/1.TestForward (5 ms)
[ RUN      ] FlattenLayerTest/1.TestSetupWithAxis
[       OK ] FlattenLayerTest/1.TestSetupWithAxis (0 ms)
[ RUN      ] FlattenLayerTest/1.TestSetup
[       OK ] FlattenLayerTest/1.TestSetup (0 ms)
[ RUN      ] FlattenLayerTest/1.
[  PASSED  ] 1162 tests.
Built target runtest

编写 demo 使用

将编译好的 caffe 配置到环境中,供 python 导入使用

export CAFFE_ROOT="/opt/caffe-master"
export PYCAFFE_ROOT="$CAFFE_ROOT/python"
export PYTHONPATH="$PYCAFFE_ROOT:$PYTHONPATH" 
export PATH="$CAFFE_ROOT/build/tools:$PYCAFFE_ROOT:$PATH"

使用 python Flask 提供服务

#!/usr/bin/env python
from flask import Flask, request, jsonify
from flask_cors import CORS
import caffe
from PIL import Image
import numpy as np
import io
from werkzeug.utils import secure_filename
import base64

app = Flask(__name__)
CORS(app)

model_def = '/home/joe/open_nsfw-master/nsfw_model/deploy.prototxt'
pretrained_model = '/home/joe/open_nsfw-master/nsfw_model/resnet_50_1by2_nsfw.caffemodel'

# Load the Caffe model
nsfw_net = caffe.Net(model_def, pretrained_model, caffe.TEST)

# Configure the transformer for preprocessing
caffe_transformer = caffe.io.Transformer({'data': nsfw_net.blobs['data'].data.shape})
caffe_transformer.set_transpose('data', (2, 0, 1))  # move image channels to outermost
caffe_transformer.set_mean('data', np.array([104, 117, 123]))  # Use the full mean array
caffe_transformer.set_raw_scale('data', 255)  # rescale from [0, 255] to [0, 1]
caffe_transformer.set_channel_swap('data', (2, 1, 0))  # swap channels from RGB to BGR


def resize_image(image_data, sz=(256, 256)):
    im = Image.open(io.BytesIO(image_data))
    if im.mode != "RGB":
        im = im.convert('RGB')
    imr = im.resize(sz, resample=Image.BILINEAR)
    fh_im = io.BytesIO()
    imr.save(fh_im, format='JPEG')
    fh_im.seek(0)
    return fh_im.getvalue()


def caffe_preprocess_and_compute(pimg, caffe_transformer=None, caffe_net=None,
                                 output_layers=None):
    if caffe_net is not None:
        if output_layers is None:
            output_layers = caffe_net.outputs

        img_data_rs = resize_image(pimg, sz=(256, 256))
        image = caffe.io.load_image(io.BytesIO(img_data_rs))

        H, W, _ = image.shape
        _, _, h, w = caffe_net.blobs['data'].data.shape
        h_off = max((H - h) // 2, 0)
        w_off = max((W - w) // 2, 0)
        crop = image[h_off:h_off + h, w_off:w_off + w, :]
        transformed_image = caffe_transformer.preprocess('data', crop)
        transformed_image.shape = (1,) + transformed_image.shape

        input_name = caffe_net.inputs[0]
        all_outputs = caffe_net.forward_all(blobs=output_layers,
                                            **{input_name: transformed_image})

        outputs = all_outputs[output_layers[0]][0].astype(float)
        return outputs
    else:
        return []


@app.route('/predict', methods=['POST'])
def predict():
    results = []
    for file in request.files.getlist('images'):
        try:
            filename = secure_filename(file.filename)
            image_data = file.read()
            encoded_img = base64.b64encode(image_data).decode('utf-8')

            scores = caffe_preprocess_and_compute(image_data, caffe_transformer=caffe_transformer, caffe_net=nsfw_net, output_layers=['prob'])
            results.append({
                'filename': filename,
                'score': float("{0:.10f}".format(scores[1])),
                'file': encoded_img
            })
        except Exception as e:
            return jsonify({'error': str(e)}), 500
    
    return jsonify([{'filename': item['filename'], 'score': item['score'], 'file': item['file']} for item in results])


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

使用 H5 提供简单的客户端

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>NSFW Image Classifier</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: #f4f4f9;
      margin: 0;
      padding: 2rem;
      color: #333;
    }

    h1 {
      text-align: center;
      margin-bottom: 2rem;
      color: #2c3e50;
    }

    form {
      display: flex;
      justify-content: center;
      gap: 1rem;
      flex-wrap: wrap;
      margin-bottom: 2rem;
    }

    input[type="file"] {
      padding: 0.5rem;
      border-radius: 5px;
      border: 1px solid #ccc;
    }

    button {
      padding: 0.6rem 1.5rem;
      font-size: 1rem;
      border: none;
      background-color: #3498db;
      color: white;
      border-radius: 5px;
      cursor: pointer;
      transition: background-color 0.3s ease;
    }

    button:hover {
      background-color: #2980b9;
    }

    #results {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
      gap: 2rem;
      justify-items: center;
    }

    .result-item {
      background: white;
      border-radius: 10px;
      box-shadow: 0 4px 8px rgba(0,0,0,0.1);
      overflow: hidden;
      text-align: center;
      padding: 1rem;
      transition: transform 0.2s ease;
    }

    .result-item:hover {
      transform: translateY(-5px);
    }

    .result-item img {
      max-width: 100%;
      height: auto;
      display: block;
      border-bottom: 1px solid #eee;
      margin-bottom: 1rem;
    }

    .score-text {
      font-weight: bold;
      color: #e74c3c;
    }

    .loading {
      text-align: center;
      font-size: 1.2rem;
      color: #888;
      display: none;
    }

    .description-text {
      font-size: 0.9rem;
      color: #555;
      margin-top: 0.5rem;
    }
  </style>
</head>
<body>

  <h1>上传图片进行敏感内容识别</h1>

  <form id="uploadForm" enctype="multipart/form-data">
    <input type="file" name="images" id="images" multiple accept="image/*"/>
    <button type="submit">Upload and Classify</button>
  </form>

  <!-- Loading Indicator -->
  <div class="loading" id="loading">Processing images, please wait...</div>

  <!-- Results Container -->
  <div id="results"></div>

  <script>
    document.getElementById('uploadForm').onsubmit = function(event) {
      event.preventDefault();

      const resultsDiv = document.getElementById('results');
      const loadingDiv = document.getElementById('loading');
      resultsDiv.innerHTML = '';
      loadingDiv.style.display = 'block';

      const formData = new FormData();
      const files = document.getElementById('images').files;

      for (let i = 0; i < files.length; i++) {
        formData.append('images', files[i]);
      }
      // 注意:将ip替换为自己提供服务的主机和端口
      fetch('http://192.168.3.87:5000/predict', {
        method: 'POST',
        body: formData,
      })
      .then(response => response.json())
      .then(data => {
        loadingDiv.style.display = 'none';
        resultsDiv.innerHTML = '';

        data.forEach(item => {
          try {
            // 去除可能的 base64 前缀
            let base64Data = item.file.split(',')[1] || item.file;

            const byteString = atob(base64Data);
            const ab = new ArrayBuffer(byteString.length);
            const ia = new Uint8Array(ab);

            for (let j = 0; j < byteString.length; j++) {
              ia[j] = byteString.charCodeAt(j);
            }

            const blob = new Blob([ia], { type: 'image/jpeg' });
            const url = URL.createObjectURL(blob);

            const imgElement = document.createElement('img');
            imgElement.src = url;

            const scoreText = document.createElement('p');
            scoreText.className = 'score-text';
            scoreText.textContent = `${item.filename} - NSFW Score: ${parseFloat(item.score).toFixed(4)}`;

            // 根据分数生成说明
            let description;
            const score = parseFloat(item.score);
            if (score <= 0.2) {
              description = "该图像被认为是安全的内容。";
            } else if (score <= 0.4) {
              description = "该图像可能包含轻微的敏感内容。";
            } else if (score <= 0.6) {
              description = "该图像可能包含中等程度的敏感内容。建议审慎处理。";
            } else if (score <= 0.8) {
              description = "该图像很可能包含较为明显的敏感内容。请注意审核。";
            } else {
              description = "该图像非常可能包含严重的敏感内容。请特别注意!";
            }
            
            const descText = document.createElement('p');
            descText.className = 'description-text';
            descText.textContent = description;

            const container = document.createElement('div');
            container.className = 'result-item';
            container.appendChild(imgElement);
            container.appendChild(scoreText);
            container.appendChild(descText); // 添加描述

            resultsDiv.appendChild(container);
          } catch (err) {
            console.error("Error processing image:", err);
          }
        });
      })
      .catch(error => {
        loadingDiv.style.display = 'none';
        console.error('Fetch error:', error);
        alert('请求处理过程中发生错误。');
      });
    };
  </script>

</body>
</html>

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高建伟-joe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值