{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Industrial Meter Reader\n", "This notebook shows how to create a industrial meter reader with OpenVINO Runtime. We use the PaddlePaddle pre-trained model [PPYOLOv2](https://github.com/PaddlePaddle/PaddleDetection/tree/release/2.4/configs/ppyolo) and [DeepLabV3P](https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.5/configs/deeplabv3p) to built-up a multiple inference task pipeline:\n", "\n", "1) Run detection model to find the meters, and crop them from the origin photo.\n", "2) Run segmentation model on these cropped meters to get the pointer and scale instance.\n", "3) Find the location of the pointer in scale map.\n", "\n", "![workflow](https://user-images.githubusercontent.com/91237924/166137115-67284fa5-f703-4468-98f4-c43d2c584763.png)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "from pathlib import Path\n", "import numpy as np\n", "import math\n", "import cv2\n", "import tarfile\n", "import matplotlib.pyplot as plt\n", "from openvino.runtime import Core\n", "\n", "sys.path.append(\"../utils\")\n", "from notebook_utils import download_file, segmentation_map_to_image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Prepare the Model and Test Image\n", "Download PPYolov2 and DeepLabV3P pre-trained models from PaddlePaddle community." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "MODEL_DIR = \"model\"\n", "DATA_DIR = \"data\"\n", "DET_MODEL_LINK = \"https://bj.bcebos.com/paddlex/examples2/meter_reader/meter_det_model.tar.gz\"\n", "SEG_MODEL_LINK = \"https://bj.bcebos.com/paddlex/examples2/meter_reader/meter_seg_model.tar.gz\"\n", "DET_FILE_NAME = DET_MODEL_LINK.split(\"/\")[-1]\n", "SEG_FILE_NAME = SEG_MODEL_LINK.split(\"/\")[-1]\n", "IMG_LINK = \"https://user-images.githubusercontent.com/91237924/170696219-f68699c6-1e82-46bf-aaed-8e2fc3fa5f7b.jpg\"\n", "IMG_FILE_NAME = IMG_LINK.split(\"/\")[-1]\n", "IMG_PATH = Path(f\"{DATA_DIR}/{IMG_FILE_NAME}\")\n", "\n", "os.makedirs(MODEL_DIR, exist_ok=True)\n", "\n", "download_file(DET_MODEL_LINK, directory=MODEL_DIR, show_progress=True)\n", "file = tarfile.open(f\"model/{DET_FILE_NAME}\")\n", "res = file.extractall(\"model\")\n", "if not res:\n", " print(f\"Detection Model Extracted to \\\"./{MODEL_DIR}\\\".\")\n", "else:\n", " print(\"Error Extracting the Detection model. Please check the network.\")\n", "\n", "download_file(SEG_MODEL_LINK, directory=MODEL_DIR, show_progress=True)\n", "file = tarfile.open(f\"model/{SEG_FILE_NAME}\")\n", "res = file.extractall(\"model\")\n", "if not res:\n", " print(f\"Segmentation Model Extracted to \\\"./{MODEL_DIR}\\\".\")\n", "else:\n", " print(\"Error Extracting the Segmentation model. Please check the network.\")\n", "\n", "download_file(IMG_LINK, directory=DATA_DIR, show_progress=True)\n", "if IMG_PATH.is_file():\n", " print(f\"Test Image Saved to \\\"./{DATA_DIR}\\\".\")\n", "else:\n", " print(\"Error Downloading the Test Image. Please check the network.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Configuration\n", "Add parameter configuration for reading calculation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "METER_SHAPE = [512, 512] \n", "CIRCLE_CENTER = [256, 256] \n", "CIRCLE_RADIUS = 250\n", "PI = math.pi\n", "RECTANGLE_HEIGHT = 120\n", "RECTANGLE_WIDTH = 1570\n", "TYPE_THRESHOLD = 40\n", "COLORMAP = np.array([[28, 28, 28], [238, 44, 44], [250, 250, 250]])\n", "\n", "# There are 2 types of meters in test image datasets\n", "METER_CONFIG = [{\n", " 'scale_interval_value': 25.0 / 50.0,\n", " 'range': 25.0,\n", " 'unit': \"(MPa)\"\n", "}, {\n", " 'scale_interval_value': 1.6 / 32.0,\n", " 'range': 1.6,\n", " 'unit': \"(MPa)\"\n", "}]\n", "\n", "SEG_LABEL = {'background': 0, 'pointer': 1, 'scale': 2}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load the Models\n", "Initialize the OpenVINO compiled model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize OpenVINO Runtime\n", "ie_core = Core()\n", "\n", "\n", "def model_init(det_model_path, seg_model_path):\n", " \"\"\"\n", " Initialize the Detection and Segmentation models\n", "\n", " :param: \n", " model (str): model path *.pdmodel\n", " :retuns:\n", " detector: detection compiled model\n", " segmenter: segmentation compiled model \n", " \n", " \"\"\"\n", "\n", " # Load the PaddlePaddle detection model directly\n", " det_model = ie_core.read_model(model=det_model_path)\n", " det_model.reshape({'image': [1, 3, 608, 608], 'im_shape': [1, 2], 'scale_factor': [1, 2]})\n", " detector = ie_core.compile_model(model=det_model, device_name=\"CPU\")\n", " \n", " # Load the PaddlePaddle segmentation model directly, the input batch size is dynamic\n", " seg_model = ie_core.read_model(model=seg_model_path)\n", " seg_model.reshape({'image': [-1, 3, 512, 512]})\n", " segmenter = ie_core.compile_model(model=seg_model, device_name=\"CPU\")\n", "\n", " return detector, segmenter" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Predictor\n", "Define a common predictor function for both detection and segmentation models" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def predictor(input, compiled_model):\n", " \"\"\"\n", " A predictor to run inference\n", "\n", " :param: \n", " input: input data\n", " compiled_model: a IE detector or segmenter\n", " :retuns:\n", " result (np.array): model output data\n", " \n", " \"\"\"\n", " output_layer = compiled_model.output(0)\n", " result = compiled_model(input)[output_layer]\n", " return result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Process\n", "Including the preprocessing and postprocessing tasks of each model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def det_preprocess(input_image, target_size):\n", " \"\"\"\n", " Preprocessing the input data for detection task\n", "\n", " :param: \n", " input_image (np.array): input data\n", " size (int): the image size required by model input layer\n", " :retuns:\n", " img.astype (np.float32): preprocessed image\n", " \n", " \"\"\"\n", " img = cv2.resize(input_image, (target_size, target_size))\n", " img = np.transpose(img, [2, 0, 1]) / 255\n", " img = np.expand_dims(img, 0)\n", " img_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1))\n", " img_std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1))\n", " img -= img_mean\n", " img /= img_std\n", " return img.astype(np.float32)\n", "\n", "\n", "def filter_bboxes(det_results, score_threshold):\n", " \"\"\"\n", " filter out the detection results with low confidence\n", "\n", " :param:\n", " det_results (list[dict]): detection results\n", " score_threshold (float): confidence threshold\n", "\n", " :retuns:\n", " filtered_results (list[dict]): filter detection results\n", " \n", " \"\"\"\n", " filtered_results = []\n", " for i in range(len(det_results)):\n", " if det_results[i, 1] > score_threshold:\n", " filtered_results.append(det_results[i])\n", " return filtered_results\n", "\n", "\n", "def roi_crop(image, results, scale_x, scale_y):\n", " \"\"\"\n", " crop the area of detected meter of original image\n", "\n", " :param:\n", " img (np.array):original image。\n", " det_results (list[dict]): detection results\n", " scale_x (float): the scale value in x axis\n", " scale_y (float): the scale value in y axis\n", "\n", " :retuns:\n", " roi_imgs (list[np.array]): the list of meter images\n", " loc (list[int]): the list of meter locations\n", " \n", " \"\"\"\n", " roi_imgs = []\n", " loc = []\n", " for result in results:\n", " bbox = result[2:]\n", " xmin, ymin, xmax, ymax = [int(bbox[0] * scale_x), int(bbox[1] * scale_y), int(bbox[2] * scale_x), int(bbox[3] * scale_y)]\n", " sub_img = image[ymin:(ymax + 1), xmin:(xmax + 1), :]\n", " roi_imgs.append(sub_img)\n", " loc.append([xmin, ymin, xmax, ymax])\n", " return roi_imgs, loc\n", "\n", "\n", "def roi_process(input_images, target_size, interp=cv2.INTER_LINEAR):\n", " \"\"\"\n", " Prepare the roi image of detection results data\n", " Preprocessing the input data for segmentation task\n", "\n", " :param:\n", " input_images (list[np.array]):the list of meter images\n", " target_size (list|tuple): height and width of resized image, e.g [heigh,width]\n", " interp (int):the interp method for image reszing\n", "\n", " :retuns:\n", " img_list (list[np.array]):the list of processed images\n", " resize_img (list[np.array]): for visualization\n", " \n", " \"\"\"\n", " img_list = list()\n", " resize_list = list()\n", " for img in input_images:\n", " img_shape = img.shape\n", " scale_x = float(target_size[1]) / float(img_shape[1])\n", " scale_y = float(target_size[0]) / float(img_shape[0])\n", " resize_img = cv2.resize(img, None, None, fx=scale_x, fy=scale_y, interpolation=interp)\n", " resize_list.append(resize_img)\n", " resize_img = resize_img.transpose(2, 0, 1) / 255\n", " img_mean = np.array([0.5, 0.5, 0.5]).reshape((3, 1, 1))\n", " img_std = np.array([0.5, 0.5, 0.5]).reshape((3, 1, 1))\n", " resize_img -= img_mean\n", " resize_img /= img_std\n", " img_list.append(resize_img)\n", " return img_list, resize_list\n", "\n", "\n", "def erode(seg_results, erode_kernel):\n", " \"\"\"\n", " erode the segmentation result to get the more clear instance of pointer and scale\n", "\n", " :param:\n", " seg_results (list[dict]):segmentation results\n", " erode_kernel (int): size of erode_kernel\n", "\n", " :return:\n", " eroded_results (list[dict]): the lab map of eroded_results\n", " \"\"\"\n", " kernel = np.ones((erode_kernel, erode_kernel), np.uint8)\n", " eroded_results = seg_results\n", " for i in range(len(seg_results)):\n", " eroded_results[i] = cv2.erode(seg_results[i].astype(np.uint8), kernel)\n", " return eroded_results\n", "\n", "\n", "def circle_to_rectangle(seg_results):\n", " \"\"\"\n", " switch the shape of label_map from circle to rectangle\n", "\n", " :param:\n", " seg_results (list[dict]):segmentation results\n", "\n", " :return:\n", " rectangle_meters (list[np.array]):the rectangle of label map\n", "\n", " \"\"\"\n", " rectangle_meters = list()\n", " for i, seg_result in enumerate(seg_results):\n", " label_map = seg_result\n", "\n", " # The size of rectangle_meter is determined by RECTANGLE_HEIGHT and RECTANGLE_WIDTH\n", " rectangle_meter = np.zeros((RECTANGLE_HEIGHT, RECTANGLE_WIDTH), dtype=np.uint8)\n", " for row in range(RECTANGLE_HEIGHT):\n", " for col in range(RECTANGLE_WIDTH):\n", " theta = PI * 2 * (col + 1) / RECTANGLE_WIDTH\n", " \n", " # The radius of meter circle will be mapped to the height of rectangle\n", " rho = CIRCLE_RADIUS - row - 1\n", " y = int(CIRCLE_CENTER[0] + rho * math.cos(theta) + 0.5)\n", " x = int(CIRCLE_CENTER[1] - rho * math.sin(theta) + 0.5)\n", " rectangle_meter[row, col] = label_map[y, x]\n", " rectangle_meters.append(rectangle_meter)\n", " return rectangle_meters\n", "\n", "\n", "def rectangle_to_line(rectangle_meters):\n", " \"\"\"\n", " switch the dimension of rectangle label map from 2D to 1D\n", "\n", " :param:\n", " rectangle_meters (list[np.array]):2D rectangle OF label_map。\n", "\n", " :return:\n", " line_scales (list[np.array]): the list of scales value\n", " line_pointers (list[np.array]):the list of pointers value\n", "\n", " \"\"\"\n", " line_scales = list()\n", " line_pointers = list()\n", " for rectangle_meter in rectangle_meters:\n", " height, width = rectangle_meter.shape[0:2]\n", " line_scale = np.zeros((width), dtype=np.uint8)\n", " line_pointer = np.zeros((width), dtype=np.uint8)\n", " for col in range(width):\n", " for row in range(height):\n", " if rectangle_meter[row, col] == SEG_LABEL['pointer']:\n", " line_pointer[col] += 1\n", " elif rectangle_meter[row, col] == SEG_LABEL['scale']:\n", " line_scale[col] += 1\n", " line_scales.append(line_scale)\n", " line_pointers.append(line_pointer)\n", " return line_scales, line_pointers\n", "\n", "\n", "def mean_binarization(data_list):\n", " \"\"\"\n", " binarize the data\n", "\n", " :param:\n", " data_list (list[np.array]):input data\n", "\n", " :return:\n", " binaried_data_list (list[np.array]):output data。\n", "\n", " \"\"\"\n", " batch_size = len(data_list)\n", " binaried_data_list = data_list\n", " for i in range(batch_size):\n", " mean_data = np.mean(data_list[i])\n", " width = data_list[i].shape[0]\n", " for col in range(width):\n", " if data_list[i][col] < mean_data:\n", " binaried_data_list[i][col] = 0\n", " else:\n", " binaried_data_list[i][col] = 1\n", " return binaried_data_list\n", "\n", "\n", "def locate_scale(line_scales):\n", " \"\"\"\n", " find out the location of center of each scale\n", "\n", " :param:\n", " line_scales (list[np.array]):the list of binaried scales value\n", "\n", " :return:\n", " scale_locations (list[list]):location of each scale\n", "\n", " \"\"\"\n", " batch_size = len(line_scales)\n", " scale_locations = list()\n", " for i in range(batch_size):\n", " line_scale = line_scales[i]\n", " width = line_scale.shape[0]\n", " find_start = False\n", " one_scale_start = 0\n", " one_scale_end = 0\n", " locations = list()\n", " for j in range(width - 1):\n", " if line_scale[j] > 0 and line_scale[j + 1] > 0:\n", " if not find_start:\n", " one_scale_start = j\n", " find_start = True\n", " if find_start:\n", " if line_scale[j] == 0 and line_scale[j + 1] == 0:\n", " one_scale_end = j - 1\n", " one_scale_location = (one_scale_start + one_scale_end) / 2\n", " locations.append(one_scale_location)\n", " one_scale_start = 0\n", " one_scale_end = 0\n", " find_start = False\n", " scale_locations.append(locations)\n", " return scale_locations\n", "\n", "\n", "def locate_pointer(line_pointers):\n", " \"\"\"\n", " find out the location of center of pointer\n", "\n", " :param:\n", " line_scales (list[np.array]):the list of binaried pointer value\n", "\n", " :return:\n", " scale_locations (list[list]):location of pointer\n", "\n", " \"\"\"\n", " batch_size = len(line_pointers)\n", " pointer_locations = list()\n", " for i in range(batch_size):\n", " line_pointer = line_pointers[i]\n", " find_start = False\n", " pointer_start = 0\n", " pointer_end = 0\n", " location = 0\n", " width = line_pointer.shape[0]\n", " for j in range(width - 1):\n", " if line_pointer[j] > 0 and line_pointer[j + 1] > 0:\n", " if not find_start:\n", " pointer_start = j\n", " find_start = True\n", " if find_start:\n", " if line_pointer[j] == 0 and line_pointer[j + 1] == 0 :\n", " pointer_end = j - 1\n", " location = (pointer_start + pointer_end) / 2\n", " find_start = False\n", " break\n", " pointer_locations.append(location)\n", " return pointer_locations\n", "\n", "\n", "def get_relative_location(scale_locations, pointer_locations):\n", " \"\"\"\n", " match the location of pointer and scales\n", "\n", " :param:\n", " scale_locations (list[list]):location of each scale\n", " pointer_locations (list[list]):location of pointer\n", "\n", " :return:\n", " pointed_scales (list[dict]): a list of dict with:\n", " 'num_scales': total number of scales\n", " 'pointed_scale': predicted number of scales\n", " \n", " \"\"\"\n", " pointed_scales = list()\n", " for scale_location, pointer_location in zip(scale_locations,\n", " pointer_locations):\n", " num_scales = len(scale_location)\n", " pointed_scale = -1\n", " if num_scales > 0:\n", " for i in range(num_scales - 1):\n", " if scale_location[i] <= pointer_location < scale_location[i + 1]:\n", " pointed_scale = i + (pointer_location - scale_location[i]) / (scale_location[i + 1] - scale_location[i] + 1e-05) + 1\n", " result = {'num_scales': num_scales, 'pointed_scale': pointed_scale}\n", " pointed_scales.append(result)\n", " return pointed_scales\n", "\n", "\n", "def calculate_reading(pointed_scales):\n", " \"\"\"\n", " calculate the value of meter according to the type of meter\n", "\n", " :param:\n", " pointed_scales (list[list]):predicted number of scales\n", "\n", " :return:\n", " readings (list[float]): the list of values read from meter\n", " \n", " \"\"\"\n", " readings = list()\n", " batch_size = len(pointed_scales)\n", " for i in range(batch_size):\n", " pointed_scale = pointed_scales[i]\n", " # find out the type of meter according the total number of scales\n", " if pointed_scale['num_scales'] > TYPE_THRESHOLD:\n", " reading = pointed_scale['pointed_scale'] * METER_CONFIG[0]['scale_interval_value']\n", " else:\n", " reading = pointed_scale['pointed_scale'] * METER_CONFIG[1]['scale_interval_value']\n", " readings.append(reading)\n", " return readings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Main Function\n", "### Intialize the model and parameters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "img_file = f\"{DATA_DIR}/{IMG_FILE_NAME}\"\n", "det_model_path = f\"{MODEL_DIR}/meter_det_model/model.pdmodel\"\n", "seg_model_path = f\"{MODEL_DIR}/meter_seg_model/model.pdmodel\"\n", "erode_kernel = 4\n", "score_threshold = 0.5\n", "seg_batch_size = 2\n", "input_shape = 608\n", "detector, segmenter = model_init(det_model_path, seg_model_path)\n", "image = cv2.imread(img_file)\n", "plt.imshow(image)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run meter detection model\n", "Detect the location of the meter and prepare the ROI images for segmentation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Prepare the input data for meter detection model\n", "im_shape = np.array([[input_shape, input_shape]]).astype('float32')\n", "scale_factor = np.array([[1, 2]]).astype('float32')\n", "input_image = det_preprocess(image, input_shape)\n", "inputs_dict = {'image': input_image, \"im_shape\": im_shape, \"scale_factor\": scale_factor}\n", "\n", "# Run meter detection model\n", "det_results = predictor(inputs_dict, detector)\n", "\n", "# Filter out the bounding box with low confidence\n", "filtered_results = filter_bboxes(det_results, score_threshold)\n", "\n", "# Prepare the input data for meter segmentation model\n", "scale_x = image.shape[1] / input_shape * 2\n", "scale_y = image.shape[0] / input_shape\n", "\n", "# Create the individual photo for each detected meter\n", "roi_imgs, loc = roi_crop(image, filtered_results, scale_x, scale_y)\n", "roi_imgs, resize_imgs = roi_process(roi_imgs, METER_SHAPE)\n", "\n", "# Create the photos of detection results\n", "if len(resize_imgs) == 2:\n", " roi_stack = np.hstack([resize_imgs[0], resize_imgs[1]])\n", "else:\n", " roi_stack = resize_imgs[0]\n", "\n", "if cv2.imwrite(f\"{DATA_DIR}/detection_results.jpg\", roi_stack):\n", " print(\"The detection result image has been saved as \\\"detection_results.jpg\\\" in data\")\n", " plt.imshow(roi_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run meter segmentation model\n", "Get the results of segmentation task on detected ROI." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "seg_results = list()\n", "num_imgs = len(roi_imgs)\n", "\n", "# Run meter segmentation model on all meters detected\n", "for i in range(0, num_imgs, seg_batch_size):\n", " batch = roi_imgs[i : min(num_imgs, i + seg_batch_size)]\n", " seg_result = predictor([batch], segmenter)\n", " seg_results.extend(seg_result)\n", "results = []\n", "for i in range(len(seg_results)):\n", " results.append(np.argmax(seg_results[i], axis=0)) \n", "seg_results = erode(results, erode_kernel)\n", "\n", "# Create the photos of segmentation results\n", "if len(resize_imgs) == 2:\n", " mask_stack = np.hstack([segmentation_map_to_image(seg_results[0], COLORMAP), \n", " segmentation_map_to_image(seg_results[1], COLORMAP)])\n", "else:\n", " mask_stack = segmentation_map_to_image(seg_results[0], COLORMAP)\n", "\n", "if cv2.imwrite(f\"{DATA_DIR}/segmentation_results.jpg\", mask_stack):\n", " print(\"The segmentation result image has been saved as \\\"segmentation_results.jpg\\\" in data\")\n", " plt.imshow(mask_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Postprocess the models result and calculate the final readings\n", "Use OpenCV function to find the location of the pointer in scale map." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Find the pointer location in scale map and calculate the meters reading \n", "rectangle_meters = circle_to_rectangle(seg_results)\n", "line_scales, line_pointers = rectangle_to_line(rectangle_meters)\n", "binaried_scales = mean_binarization(line_scales)\n", "binaried_pointers = mean_binarization(line_pointers)\n", "scale_locations = locate_scale(binaried_scales)\n", "pointer_locations = locate_pointer(binaried_pointers)\n", "pointed_scales = get_relative_location(scale_locations, pointer_locations)\n", "meter_readings = calculate_reading(pointed_scales)\n", "\n", "# Plot the rectangle meters\n", "if len(rectangle_meters) == 2:\n", " rectangle_meters_stack = np.hstack([segmentation_map_to_image(rectangle_meters[0], COLORMAP),\n", " segmentation_map_to_image(rectangle_meters[1], COLORMAP)])\n", "else:\n", " rectangle_meters_stack = segmentation_map_to_image(rectangle_meters[0], COLORMAP)\n", "\n", "if cv2.imwrite(f\"{DATA_DIR}/rectangle_meters.jpg\", rectangle_meters_stack):\n", " print(\"The rectangle_meters result image has been saved as \\\"rectangle_meters.jpg\\\" in data\")\n", " plt.imshow(rectangle_meters_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Get the reading result on the meter picture" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create final result photo with meter value\n", "for i in range(len(meter_readings)):\n", " print(\"Meter {}: {:.3f}\".format(i + 1, meter_readings[i]))\n", " \n", "result_image = image.copy()\n", "for i in range(len(loc)):\n", " cv2.rectangle(result_image,(loc[i][0], loc[i][1]), (loc[i][2], loc[i][3]), (0, 150, 0), 3)\n", " font = cv2.FONT_HERSHEY_SIMPLEX\n", " cv2.rectangle(result_image, (loc[i][0], loc[i][1]), (loc[i][0] + 100, loc[i][1] + 40), (0, 150, 0), -1)\n", " cv2.putText(result_image, \"#{:.3f}\".format(meter_readings[i]), (loc[i][0],loc[i][1] + 25), font, 0.8, (255, 255, 255), 2, cv2.LINE_AA)\n", "if cv2.imwrite(f\"{DATA_DIR}/reading_results.jpg\", result_image):\n", " print(\"The reading results image has been saved as \\\"reading_results.jpg\\\" in data\")\n", " plt.imshow(result_image)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ## Try it with your meter photos!" ] } ], "metadata": { "interpreter": { "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a" }, "kernelspec": { "display_name": "Python 3.7.1 64-bit", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.13" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }