5

I am trying to build an OCR for recognising seven segment display as mentioned below

Original Image

Using preprocessing tools of open CV I got it here

threshold

Now I am trying to follow this tutorial - https://www.pyimagesearch.com/2017/02/13/recognizing-digits-with-opencv-and-python/

But on the part

digitCnts = contours.sort_contours(digitCnts,
    method="left-to-right")[0]
digits = []

I am getting error as -

The error is solved using THRESH_BINARY_INV but still the OCR is not working any fix would be great

File "/Users/ms/anaconda3/lib/python3.6/site-packages/imutils/contours.py", line 25, in sort_contours key=lambda b: b1[i], reverse=reverse))

ValueError: not enough values to unpack (expected 2, got 0)

Any idea how to solve this and make my OCR a working model

My whole code is :

import numpy as np 
import cv2
import imutils
# import the necessary packages
from imutils.perspective import four_point_transform
from imutils import contours
import imutils
import cv2

# define the dictionary of digit segments so we can identify
# each digit on the thermostat
DIGITS_LOOKUP = {
    (1, 1, 1, 0, 1, 1, 1): 0,
    (0, 0, 1, 0, 0, 1, 0): 1,
    (1, 0, 1, 1, 1, 1, 0): 2,
    (1, 0, 1, 1, 0, 1, 1): 3,
    (0, 1, 1, 1, 0, 1, 0): 4,
    (1, 1, 0, 1, 0, 1, 1): 5,
    (1, 1, 0, 1, 1, 1, 1): 6,
    (1, 0, 1, 0, 0, 1, 0): 7,
    (1, 1, 1, 1, 1, 1, 1): 8,
    (1, 1, 1, 1, 0, 1, 1): 9
}

# load image
image = cv2.imread('d4.jpg')
# create hsv
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

 # set lower and upper color limits
low_val = (60,180,160)
high_val = (179,255,255)
# Threshold the HSV image 
mask = cv2.inRange(hsv, low_val,high_val)
# find contours in mask
ret, cont, hierarchy = cv2.findContours(mask,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# select the largest contour
largest_area = 0
for cnt in cont:
    if cv2.contourArea(cnt) > largest_area:
        cont = cnt
        largest_area = cv2.contourArea(cnt)

# get the parameters of the boundingbox
x,y,w,h = cv2.boundingRect(cont)

# create and show subimage
roi = image[y:y+h, x:x+w]
cv2.imshow("Result", roi)


#  draw box on original image and show image
cv2.rectangle(image, (x,y),(x+w,y+h), (0,0,255),2)
cv2.imshow("Image", image)

grayscaled = cv2.cvtColor(roi,cv2.COLOR_BGR2GRAY)
retval, threshold = cv2.threshold(grayscaled, 10, 255, cv2.THRESH_BINARY)
retval2,threshold2 = cv2.threshold(grayscaled,125,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('threshold',threshold2)
cv2.waitKey(0)
cv2.destroyAllWindows()
# find contours in the thresholded image, then initialize the
# digit contours lists
cnts = cv2.findContours(threshold2.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
digitCnts = []

# loop over the digit area candidates
for c in cnts:
    # compute the bounding box of the contour
    (x, y, w, h) = cv2.boundingRect(c)
    # if the contour is sufficiently large, it must be a digit
    if w >= 15 and (h >= 30 and h <= 40):
        digitCnts.append(c)
# sort the contours from left-to-right, then initialize the
# actual digits themselves
digitCnts = contours.sort_contours(digitCnts,
    method="left-to-right")[0]
digits = []


# loop over each of the digits
for c in digitCnts:
    # extract the digit ROI
    (x, y, w, h) = cv2.boundingRect(c)
    roi = thresh[y:y + h, x:x + w]

    # compute the width and height of each of the 7 segments
    # we are going to examine
    (roiH, roiW) = roi.shape
    (dW, dH) = (int(roiW * 0.25), int(roiH * 0.15))
    dHC = int(roiH * 0.05)

    # define the set of 7 segments
    segments = [
        ((0, 0), (w, dH)),  # top
        ((0, 0), (dW, h // 2)), # top-left
        ((w - dW, 0), (w, h // 2)), # top-right
        ((0, (h // 2) - dHC) , (w, (h // 2) + dHC)), # center
        ((0, h // 2), (dW, h)), # bottom-left
        ((w - dW, h // 2), (w, h)), # bottom-right
        ((0, h - dH), (w, h))   # bottom
    ]
    on = [0] * len(segments)

    # loop over the segments
    for (i, ((xA, yA), (xB, yB))) in enumerate(segments):
        # extract the segment ROI, count the total number of
        # thresholded pixels in the segment, and then compute
        # the area of the segment
        segROI = roi[yA:yB, xA:xB]
        total = cv2.countNonZero(segROI)
        area = (xB - xA) * (yB - yA)

        # if the total number of non-zero pixels is greater than
        # 50% of the area, mark the segment as "on"
        if total / float(area) > 0.5:
            on[i]= 1

    # lookup the digit and draw it on the image
    digit = DIGITS_LOOKUP[tuple(on)]
    digits.append(digit)
    cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 1)
    cv2.putText(output, str(digit), (x - 10, y - 10),
        cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 255, 0), 2)
# display the digits
print(u"{}{}.{}{}.{}{} \u00b0C".format(*digits))
cv2.imshow("Input", image)
cv2.imshow("Output", output)
cv2.waitKey(0)

A help would be great in fixing my OCR

Mansi Shukla
  • 377
  • 3
  • 23
  • 1
    If you look at the OpenCV documentation on [Contours](https://docs.opencv.org/3.4/d4/d73/tutorial_py_contours_begin.html), it mentions that objects to be found should be white and background should be black. Try using, `THRESH_BINARY_INV` instead of `THRESH_BINARY`. The error says that it didn't find any contours. – Rick M. Feb 12 '19 at 12:32
  • But still it's not printing anything I asked for a fix for OCR can you please run it on your system and verify what's the error please @RickM. – Mansi Shukla Feb 12 '19 at 16:07
  • The issue mentioned by Rick is definitely relevant. Going from there, some debugging effort from your side wouldn't hurt (even if it's just simple `print`ing of the variables to console). | Your filtering criteria `w >= 15 and (h >= 30 and h <= 40)` are simply too strict. Some of the valid segments are too narrow (e.g. both pieces of `1` and the bottom piece of `7` are only some 6 pixels wide), The other pieces that are wide enough are either too short (e.g. top piece of `7` is only 18x19), or too tall (the `5`s and the `8` are 41 pixels tall). – Dan Mašek Feb 12 '19 at 22:22
  • 1
    If you are interested in implementing this sorting functionality on your own(to get more control over it), then you can refer to this [answer](https://stackoverflow.com/a/39445901/3051961) – ZdaR Feb 13 '19 at 01:07
  • Can you please just answer with a whole code it would be great I am new to this just following the tutorial . A help would be great @DanMašek – Mansi Shukla Feb 13 '19 at 03:54
  • I gave it a try and there is definitely an issue related to morphological operations in your code. Try going through [this link](https://docs.opencv.org/3.4/d9/d61/tutorial_py_morphological_ops.html), I am sure you can solve it yourself. I'll wait for you to find a solution until the last day of the bounty, then I will post my answer. Good Luck! – Rick M. Feb 13 '19 at 12:49
  • @RickM. I tried my best but unable to do so . It would be great if you can help me out here – Mansi Shukla Feb 16 '19 at 15:52
  • @RickM. well it's the last day of bounty – Mansi Shukla Feb 18 '19 at 05:36
  • 1
    @MansiShukla I am gonna put up what I tried by the end of the day :) – Rick M. Feb 18 '19 at 08:20
  • @MansiShukla Try to figure out the problem with the LUT and let us know, I am very interested in seeing what the problem there is. Apologies that this approach (with morphological operations) might not be the most robust method but I hope you got the idea :) – Rick M. Feb 18 '19 at 15:58

2 Answers2

2

I think the lookup-table you created is is for seven-digit display, not for seven-digit OCR. As for the size of display is fixed, I think you can try to segment it into seperated regions and recognise using template-matching or k-means.

This is my preprocessed steps:

(1) Find light green display in the HSV

mask = cv2.inRange(hsv, (50, 100, 180), (70, 255, 255))

enter image description here enter image description here

(2) try to seperate by projecting and recognise standard seven-digits using LUT: enter image description here enter image description here

(3) try on the detected green display

enter image description here

Kinght 金
  • 17,681
  • 4
  • 60
  • 74
  • @MansiShukla take a look [here](https://stackoverflow.com/questions/45070661/pythonopencv-how-to-plot-hsv-range) and [here](https://stackoverflow.com/questions/9413216/simple-digit-recognition-ocr-in-opencv-python). Then connect the two things ;) – lucians Feb 18 '19 at 21:03
1

So, as I said in the comments, there were two problems:

  1. You were trying to find black contours on a white background, which is opposite of OpenCV documentation. This was solved using THRESH_BINARY_INV flag instead of THRESH_BINARY.

  2. Due to the numbers not being connected, a full contour for the number couldn't be found. So I tried some morphological operations. Following are the steps:

First Threshold

2a) Opening on the above image with following code:

threshold2 = cv2.morphologyEx(threshold, cv2.MORPH_OPEN, np.ones((3,3), np.uint8))

Opening

2b) Dilation on the previous image:

threshold2 = cv2.dilate(threshold2, np.ones((5,1), np.uint8), iterations=1)

Dilation

2c) Crop the top part of the image to separate numbers due to dilating into the top border:

height, width = threshold2.shape[:2]
threshold2 = threshold2[5:height,5:width]

Note Somehow, the images are being displayed here without the white border that I am talking about. Try opening the image in a new window and you will see what I mean.

Final cropping

So, after solving these issues, the contours were pretty good and how they were supposed to be as seen here:

cnts = cv2.findContours(threshold2.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

digitCnts = []

# loop over the digit area candidates
for c in cnts:
    # compute the bounding box of the contour
    (x, y, w, h) = cv2.boundingRect(c)
    # if the contour is sufficiently large, it must be a digit
    if w <= width * 0.5 and (h >= height * 0.2):
        digitCnts.append(c)
# sort the contours from left-to-right, then initialize the
# actual digits themselves
cv2.drawContours(image2, digitCnts, -1, (0, 0, 255))
cv2.imwrite("cnts-sort.jpg", image2)

As you can see below, the contours are being drawn in red.

Contours

Now, for estimating whether the digit is a code or not, this part somehow doesn't work and I blame the look-up table for it. As you can see from the below images, the bounding rects for all the numbers are correctly cropped but the lookup table fails to recognize them.

# loop over each of the digits
j = 0
for c in digitCnts:
    # extract the digit ROI
    (x, y, w, h) = cv2.boundingRect(c)
    roi = threshold2[y:y + h, x:x + w]
    cv2.imwrite("roi" + str(j) + ".jpg", roi)
    j += 1

    # compute the width and height of each of the 7 segments
    # we are going to examine
    (roiH, roiW) = roi.shape
    (dW, dH) = (int(roiW * 0.25), int(roiH * 0.15))
    dHC = int(roiH * 0.05)

    # define the set of 7 segments
    segments = [
        ((0, 0), (w, dH)),  # top
        ((0, 0), (dW, h // 2)), # top-left
        ((w - dW, 0), (w, h // 2)), # top-right
        ((0, (h // 2) - dHC) , (w, (h // 2) + dHC)), # center
        ((0, h // 2), (dW, h)), # bottom-left
        ((w - dW, h // 2), (w, h)), # bottom-right
        ((0, h - dH), (w, h))   # bottom
    ]
    on = [0] * len(segments)

    # loop over the segments
    for (i, ((xA, yA), (xB, yB))) in enumerate(segments):
        # extract the segment ROI, count the total number of
        # thresholded pixels in the segment, and then compute
        # the area of the segment
        segROI = roi[yA:yB, xA:xB]
        total = cv2.countNonZero(segROI)
        area = (xB - xA) * (yB - yA)

        # if the total number of non-zero pixels is greater than
        # 50% of the area, mark the segment as "on"
        if area != 0:
            if total / float(area) > 0.5:
                on[i] = 1

    # lookup the digit and draw it on the image
    try:
        digit = DIGITS_LOOKUP[tuple(on)]
        digits.append(digit)
        cv2.rectangle(roi, (x, y), (x + w, y + h), (0, 255, 0), 1)
        cv2.putText(roi, str(digit), (x - 10, y - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 255, 0), 2)
    except KeyError:
        continue

I read through the website you mentioned in the question and from the comments it seems some of the entries in the LUT might be wrong. So I am going to leave it to you to figure that out. Following are the individual digits found (but not recognised):

1 7 5 8 5 1 1

Alternatively, you could use tesseract instead to recognise these detected digits.

Hope it helps!

Rick M.
  • 3,045
  • 1
  • 21
  • 39