
Automatic Meter Reading (AMR) is a technology that automatically collects consumption and status data from a water meter or energy metering devices (gas, electric).
AMR has a two-step approach for object detection and object recognition. Region of interest (ROI) is the detection of digits, and many studies have adopted YOLO, a CNN-based object detection system, to detect digits.
In this scenario, we developed a front-end application with a bounded region that captures a region of interest (ROI), thus we decided to consider only an object recognition approach. For the object recognition, we used a deep learning model, which is part of a broader machine learning model using neural network in the SAP BTP ecosystem.
python == 3.7.0
tensorflow==2.5.0
opencv-python == 4.5.5.62
numpy==1.19.5
import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.layers import LSTM #python ==3.7 mandatory, (python==3.8.12, not mandatory)
from PIL import ImageFont, ImageOps, Image
import json
import cv2
import random
from hdfs import InsecureClient
import io
from io import BytesIO
from PIL import Image
# Converting to TFLite
from tensorflow import lite
Training pipeline
Example of dataset
def split_data(images, labels, train_size=0.9, shuffle=True):
# 1. Get the total size of the dataset
size = len(images)
# 2. Make an indices array and shuffle it, if required
indices = np.arange(size)
if shuffle:
np.random.shuffle(indices)
# 3. Get the size of training samples
train_samples = int(size * train_size)
# 4. Split data into training and validation sets
x_train, y_train = images[indices[:train_samples]], labels[indices[:train_samples]]
x_valid, y_valid = images[indices[train_samples:]], labels[indices[train_samples:]]
return x_train, x_valid, y_train, y_valid
# to send metrics to the Submit Metrics operator, create a Python dictionary of key-value pairs
client = InsecureClient('http://datalake:50070')
directory='/shared/ml/images/'
def encode_single_sample(img_path, label):
img_path = tf.get_static_value(img_path).decode('utf-8')
label = tf.get_static_value(label).decode('utf-8')
with client.read(img_path) as reader:
# 1. Read image
img_arr = reader.read()
img = np.array(img_arr)
# 2. Decode or convert to grayscale
img = tf.image.decode_png(img, channels=3)
# 3. Convert to float32 in [0, 1] range
img = tf.image.convert_image_dtype(img, tf.float32)
# 4. Resize to the desired size
img = tf.image.resize(img, [img_height, img_width])
# 4.1. brightness
img = tf.image.adjust_brightness(img, delta=0.1)
# 4.2 sharpness
img = tf.image.random_contrast(img, 1.2, 1.5)
# 4.3 increase quality
img = tf.image.adjust_jpeg_quality(img, 75)
# 5. Transpose the image because we want the time
# dimension to correspond to the width of the image.
img = tf.transpose(img, perm=[1, 0, 2])
# 6. Map the characters in label to numbers
label = char_to_num(tf.strings.unicode_split(label, input_encoding="UTF-8"))
# 7. Return a dict as our model is expecting two inputs
return {"image": img, "label": label}
batch_size = 16
train_dataset = tf.data.Dataset.from_tensor_slices((np.array(dataset_train['image']), np.array(dataset_train['label'])))
train_dataset = (
train_dataset.map(
encode_single_sample, num_parallel_calls=tf.data.experimental.AUTOTUNE)
.batch(batch_size)
.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
)
When capturing the images, recognizing the sequence of digits is important. We use Convolutional Recurrent Neural Network (CRNN) to build deep learning model. Since CRNN models are network architectures specifically designed to recognize sequence-like objects in images, CRNN is suitable for our AMR scenario. CRNN is effective with smaller model and remarkable performance without predefined lexicon, compared to conventional methods such as CNN and RNN based algorithms.
CRNN architecture(reference link to here)
# Maximum length of any captcha in the dataset
max_length = max([len(label) for label in train_labels])
# Create characters
characters = ['0','1', '2', '3', '4', '5', '6', '7', '8', '9']
# Mapping characters to integers
char_to_num = layers.experimental.preprocessing.StringLookup(
vocabulary=list(characters), num_oov_indices=0, mask_token=None
)
Model: "model_v1"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
image (InputLayer) [(None, 350, 50, 3)] 0
__________________________________________________________________________________________________
Conv1 (Conv2D) (None, 350, 50, 32) 896 image[0][0]
__________________________________________________________________________________________________
pool1 (MaxPooling2D) (None, 175, 25, 32) 0 Conv1[0][0]
__________________________________________________________________________________________________
Conv2 (Conv2D) (None, 175, 25, 64) 18496 pool1[0][0]
__________________________________________________________________________________________________
pool2 (MaxPooling2D) (None, 87, 12, 64) 0 Conv2[0][0]
__________________________________________________________________________________________________
reshape (Reshape) (None, 87, 768) 0 pool2[0][0]
__________________________________________________________________________________________________
dense1 (Dense) (None, 87, 64) 49216 reshape[0][0]
__________________________________________________________________________________________________
dropout_4 (Dropout) (None, 87, 64) 0 dense1[0][0]
__________________________________________________________________________________________________
bidirectional_8 (Bidirectional) (None, 87, 256) 197632 dropout_4[0][0]
__________________________________________________________________________________________________
bidirectional_9 (Bidirectional) (None, 87, 128) 164352 bidirectional_8[0][0]
__________________________________________________________________________________________________
label (InputLayer) [(None, None)] 0
__________________________________________________________________________________________________
dense2 (Dense) (None, 87, 11) 1419 bidirectional_9[0][0]
__________________________________________________________________________________________________
ctc_loss (CTCLayer) (None, 87, 11) 0 label[0][0]
dense2[0][0]
==================================================================================================
Total params: 432,011
Trainable params: 432,011
Non-trainable params: 0
__________________________________________________________________________________________________
### Get the model
model = build_model()
# Get the prediction model by extracting layers till the output layer
prediction_model = keras.models.Model(
model.get_layer(name="image").input, model.get_layer(name="dense2").output
)
#Convert to TFLite
converter = lite.TFLiteConverter.from_keras_model(prediction_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.experimental_new_converter=True
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS
,tf.lite.OpsSet.SELECT_TF_OPS]
tfmodel = converter.convert()
metrics_dict = {"kpi1": "1"}
# send the metrics to the output port - Submit Metrics operator will use this to persist the metrics
api.send("metrics", api.Message(metrics_dict))
# create & send the model blob to the output port
model_blob = tfmodel
#model_blob = bytes(tfmodel, 'utf-8') # error
api.send("modelBlob", model_blob)
Writing to HANA pipeline
import tensorflow as tf
print(tf.__version__)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
24 | |
23 | |
22 | |
15 | |
12 | |
10 | |
9 | |
7 | |
7 | |
7 |