上传图片进行敏感内容识别
预览效果

环境准备
- Ubuntu 16.04
- python 2.7.12
- caffe 1.0.0
安装调试环境:
bash
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 源码, 并编译好:
bash
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)"
测试是否正确编译:
bash
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 导入使用:
bash
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 提供服务:
python
#!/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 提供简单的客户端:
html
<!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>