基于 Keras 卷积神经网络的图片识别

卷积神经网络

我们知道图片是由大量像素点构成的,每个像素点又由 RGB 颜色值构成,用计算机识别图片,如果直接分析各个像素点和它们的颜色值,计算量就非常大,分析会很复杂和低效,卷积神经网络 (CNN,Convolutional Neural Network) 通过卷积运算快速从大量数据中提取出各种特征,然后用神经网络对浓缩后的特征数据进行快速分析,就能高效地处理图片。

Keras 是 TensorFlow 中一个高层神经网络 API,用 Keras 可以很方便地构建和训练神经网络模型,官方也提供了在 ImageNet 数据集上训练的 ResNet-50 模型,ResNet 叫残差网络,ResNet-50 是一种深度卷积神经网络,该神经网络模型既轻量又有很高的识别准确率。

图片识别 API

这里基于 Keras 的预训练模型 ResNet-50 做一个图片识别应用,为了易于使用,我们把图片识别部分封装成 RESTful API,客户端通过 Http 请求把图片传给后端,服务端完成识别后把结果返回给客户端,客户端再用某种可视化的方式展示出来。

用 Python 实现后端 API 的封装,主要包括以下几步:

加载模型

1
2
3
from tensorflow.keras.applications import ResNet50

model = ResNet50(weights="imagenet")

图片预处理

ResNet50 模型的默认输入尺寸是 224x224 像素,输入格式为 RGB,故需要对输入图片做一些预处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.applications import imagenet_utils
import numpy as np


def prepare_image(image, target):
# if the image mode is not RGB, convert it
if image.mode != "RGB":
image = image.convert("RGB")

# resize the input image and preprocess it
image = image.resize(target)
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
image = imagenet_utils.preprocess_input(image)
return image

创建 RESTful API

Flask 是一个轻量级的 Python Web 应用框架,用 Flask 创建一个 POST 接口,用于接收客户端传过来的图片文件,送入模型进行识别,然后把识别结果返回给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask, request, jsonify
from PIL import Image
import io

app = Flask(__name__)

@app.route("/predict", methods=["POST"])
def predict():
data = {"success": False}
if request.files.get("image"):
image = request.files["image"].read()
image = Image.open(io.BytesIO(image))
image = prepare_image(image, target=(224, 224))

preds = model.predict(image)
results = imagenet_utils.decode_predictions(preds)

data["predictions"] = []
for (_, label, prob) in results[0]:
r = {"label": label, "probability": float(prob)}
data["predictions"].append(r)
data["success"] = True

return jsonify(data)


if __name__ == "__main__":
app.run(host='0.0.0.0')

Vue 客户端

用 Vue.js 开发一个 Web 客户端,实现图片上传和识别结果展示。

为了贯彻 Vue 组件化开发思想,我把单一页面分成三个子组件,分别是图片选择、图片预览和结果显示三个组件,然后放置在一个父组件中,组件间通过 Vuex 共享状态。

核心代码

首先从电脑硬盘中选取一张图片,组件获取图片后,提交 mutation 更新 store 中的状态,把图片 URL 传递给预览组件:

1
2
3
4
5
6
7
8
9
getFile(event) {
let image = event.target.files[0];
let imgurl = window.URL.createObjectURL(image);
this.$store.commit("updateData", {
img: imgurl,
ifsucc: 1
});
this.postimg(image);
}

同时请求 Flask 服务端接口,并把返回结果传递给图表子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let fdata = new FormData();
fdata.append("image", image);
let _this = this;
axios.post("/predict", fdata, {
headers: { "Content-Type": "multipart/form-data" }
})
.then(res => {
if (res.data.success) {
_this.$store.commit("updateData", {
ifsucc: 2,
possblity: res.data.predictions
});
} else {
_this.$store.commit("updateData", {
ifsucc: 3,
msg: "图片无法识别"
});
}
})
.catch(error => {
_this.$store.commit("updateData", {
ifsucc: 4,
msg: error.toString()
});
});

快速体验

这里我已将前后端整体打包为一个 Docker 镜像,你可以直接运行一个 Docker 容器来体验:

1
docker run -p 80:80 yunterry/keras-vue

前端页面:

Android 客户端

通过 Android 客户端你可以直接用手机摄像头拍摄一张照片,或是从手机相册中选取一张照片进行识别,这里用 Retrofit、Picasso 开发一个 Android APP。

核心代码

先从手机相册中选取一张图片,显示在图片预览控件中:

1
2
3
String imgpath = data.getStringArrayListExtra(
MultiImageSelectorActivity.EXTRA_RESULT).get(0);
Picasso.with(this).load("file://" + imgpath).into(imgView);

同时向服务端发起 Post 请求,把图片传过去,请求返回即为识别结果,根据标签和概率显示在列表控件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Rest.getRestApi().uploadFile(requestImgPart)
.enqueue(new Callback<Result>() {
@Override
public void onResponse(Call<Result> call, Response<Result> response) {
recyclerView.setAdapter(new CommonAdapter<Result.PredictionsBean>(MainActivity.this,
R.layout.img_item, response.body().predictions) {
@Override
protected void convert(ViewHolder holder, Result.PredictionsBean pred, int position) {
holder.setProgress(R.id.progress, (int) Math.round(pred.probability * 10000));
holder.setText(R.id.object, pred.label);
}
});
}

@Override
public void onFailure(Call<Result> call, Throwable t) {
Toast.makeText(MainActivity.this, "请求失败",
Toast.LENGTH_LONG).show();
}
});

运行结果如下:

项目源码

Vue 客户端

https://github.com/yunTerry/Keras-Vue

Android 客户端

https://github.com/yunTerry/Keras-Android

参考文章

https://blog.keras.io