Home > Mobile >  Masking problems in 6 digit recognition using OpenCV with lighted meter
Masking problems in 6 digit recognition using OpenCV with lighted meter

Time:10-06

I am trying to recognize six digits from a meter using python-OpenCV. It's surprising how incredibly hard it is to set morphological operations working in the right way, given the time I have spent adjusting the focus/distance of my raspberry pi camera to the meter screen and I even have bought a separate led lamp to have as much uniform light as possible. This is a template image enter image description here and I've tried using and adjusting the code from these two sources: enter image description here

import cv2
import numpy as np

# load image
img = cv2.imread("digits.jpg")

# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# blur
blur = cv2.GaussianBlur(gray, (0,0), sigmaX=51, sigmaY=51)

# divide
divide = cv2.divide(gray, blur, scale=255)

# threshold  
thresh = cv2.threshold(divide, 235, 255, cv2.THRESH_BINARY)[1]

# apply morphology
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)

# write result to disk
cv2.imwrite("digits_division.jpg", divide)
cv2.imwrite("digits_threshold.jpg", thresh)
cv2.imwrite("digits_morph.jpg", morph)

# display it
cv2.imshow("divide", divide)
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.waitKey(0)
cv2.destroyAllWindows()

Division normalized image:

enter image description here

Thresholded image:

enter image description here

Morphology processed image:

enter image description here

You can then clean up further by getting contours and removing small contours and very long horizontal contours.

CodePudding user response:

The key to getting this working is cleaning the image up which I have done to a good enough level to get it to work. I've done this using scikit image library.

I then look at certain squares on the image and take an average reading from that area. On the right hand-side image I've marked some of the locations with red squares.

enter image description here

My script I used to get this result:

import numpy as np
from pathlib import Path
import imageio.v3 as iio
import skimage.filters as skif
from skimage.color import rgb2gray
from skimage.util import img_as_ubyte
from skimage.restoration import denoise_bilateral

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import matplotlib.cm as cm

threshold = 125
digit_loc = [1600, 1300, 1000, 730, 420, 155]
size = 20
x_mid = 80
x_right = 160
y_top = 130
y_mt = 250
y_mid = 380
y_bm = 520
y_bot = 630


def img_with_threshold(orig_img):
    block_size = 255
    local_thresh = skif.threshold_local(
        orig_img,
        block_size,
        method="mean",
    )
    binary_local = orig_img > local_thresh

    u8_val = img_as_ubyte(binary_local)
    return u8_val


def image_denoise(orig_img):
    return denoise_bilateral(orig_img, win_size=10, bins=10, )


def plot_imgs(orig_img, mod_img):
    # Display the image
    fig, axes = plt.subplots(1, 2, figsize=(8, 8), sharex=True, sharey=True)
    ax = axes.ravel()
    ax[0].imshow(orig_img, cmap=cm.Greys_r)
    ax[1].imshow(mod_img, cmap=cm.Greys_r)
    # Create a Rectangle patch
    for x_loc in digit_loc:
        rect1 = Rectangle((x_loc   x_mid, y_top), size, size, linewidth=1, edgecolor='r', facecolor='none')
        rect2 = Rectangle((x_loc, y_mt), size, size, linewidth=1, edgecolor='r', facecolor='none')
        rect3 = Rectangle((x_loc   x_right, y_mt), size, size, linewidth=1, edgecolor='r', facecolor='none')
        rect4 = Rectangle((x_loc   x_mid, y_mid), size, size, linewidth=1, edgecolor='r', facecolor='none')
        rect5 = Rectangle((x_loc, y_bm), size, size, linewidth=1, edgecolor='r', facecolor='none')
        rect6 = Rectangle((x_loc   x_right, y_bm), size, size, linewidth=1, edgecolor='r', facecolor='none')
        rect7 = Rectangle((x_loc   x_mid, y_bot), size, size, linewidth=1, edgecolor='r', facecolor='none')

        # Add the patch to the Axes
        ax[1].add_patch(rect1)
        ax[1].add_patch(rect2)
        ax[1].add_patch(rect3)
        ax[1].add_patch(rect4)
        ax[1].add_patch(rect5)
        ax[1].add_patch(rect6)
        ax[1].add_patch(rect7)

    plt.show()


def seg_to_digit(segments, location):
    digit_values = {0b1110111: 0,
                    0b0010010: 1,
                    0b1011101: 2,
                    0b1011011: 3,
                    0b0111010: 4,
                    0b1101011: 5,
                    0b1101111: 6,
                    0b1110010: 7,
                    0b1111111: 8,
                    0b1111011: 9,
                    }
    result = int("".join(["1" if i < threshold else "0" for i in segments]), 2)
    # print("score:", result)
    return digit_values.get(result, 0) * 10 ** location


def get_digit(location, mod_img):
    """
      a
    b  c
     d
    e  f
     g
    """
    x_loc = digit_loc[location]
    m_loc = (x_loc   x_mid, x_loc   x_mid   size)
    l_loc = (x_loc, x_loc   size)
    r_loc = (x_loc   x_right, x_loc   x_right   size)
    seg_a = np.average(mod_img[y_top:y_top   size, m_loc[0]:m_loc[1]])
    seg_b = np.average(mod_img[y_mt:y_mt   size, l_loc[0]:l_loc[1]])
    seg_c = np.average(mod_img[y_mt:y_mt   size, r_loc[0]:r_loc[1]])
    seg_d = np.average(mod_img[y_mid:y_mid   size, m_loc[0]:m_loc[1]])
    seg_e = np.average(mod_img[y_bm:y_bm   size, l_loc[0]:l_loc[1]])
    seg_f = np.average(mod_img[y_bm:y_bm   size, r_loc[0]:r_loc[1]])
    seg_g = np.average(mod_img[y_bot:y_bot   size, m_loc[0]:m_loc[1]])
    segments = [seg_a, seg_b, seg_c, seg_d, seg_e, seg_f, seg_g]
    # print(f"x loc: {x_loc}, digit index: {location}, segment values: {segments}")
    # create an integer from the bits
    # print('value:', result)
    return seg_to_digit(segments, location)


def main():
    data_dir = Path(__file__).parent.joinpath('data')
    meter_img = data_dir.joinpath('meter_test.jpg')
    img = iio.imread(meter_img)
    gray_img = img_as_ubyte(rgb2gray(img))
    img_result = image_denoise(gray_img)
    img_result1 = img_with_threshold(img_result)
    reading = 0
    for dig_loc in range(6):
        reading  = get_digit(dig_loc, img_result1)
        print(f"{reading:>21}")
    print("Final reading:", reading)

    plot_imgs(gray_img, img_result1)


if __name__ == '__main__':
    main()

This gave the following output:

                    7
                   77
                  677
                 4677
                24677
               924677
Final reading: 924677
  • Related