lcd

package module
v0.0.0-...-c3cd7a6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2021 License: Apache-2.0 Imports: 17 Imported by: 0

README

LCD

Library to decode 7 segment characters in images.

Configuration

This library is designed to decode 7 segment digits in images. The decoder operates by configuring a template that defines the outline of a digit as a quadrilateral, and the width of the segments. This template is then used to identify one or more 7 segment display digits in an image.

For example, given the image: lcd There are 6 digits in the display that are all the same size, so a digit template can be used to define the size and shape of the digits, and then a definition of 6 digits that use the template as a base.

The configuration that defines this is:

lcd=A,81,0,70,136,-9,135,21,85,127
digit=A,281,253
digit=A,394,251
digit=A,508,252
digit=A,617,252
digit=A,731,251
digit=A,842,252

(The configuration uses the config library to parse the lines in the file configuration). The core library does not require the use of this config library - the library can be configured discretely using method calls.

The first line defines a digit template, which is expressed as a name or tag ('A' in this instance), 3 pairs of (X, Y) co-ordinates, a pixel width of the segments (21 in this case), and an optional (X, Y) co-ordinate defining the centre of the decimal place. All of the (X, Y) co-ordinates are offsets from a base point (0, 0) that represents the top left corner of the digit. Since the top left corner in the template is always considered (0, 0), it is not included in the template definition. The first 3 pairs of co-ordinates define the top right, bottom right and bottom left of the outline of the digit. The final (optional) pair indicates the location of the decimal place. If this pair is not present, no decimal place is assumed. The following image shows how the dimensions of the digit are calculated in the template: lcd

After the template, 6 separate lines define the 6 digits in the image, each of which uses the template named 'A' to define the size of the digit, and a (X, Y) co-ordinate pair that defines the top left of the digit.

To make it easy to verify the location of the digits, a utility program named sample is provided that reads a configuration and overlays on an image where the digits are e.g run thus:

./sample --input=lcd6.jpg --config=lcd6.config --fill=false

generates the file output.jpg: lcd where each segment's corner has a white arrow placed on it. Running the program with the fill option turned on:

./sample --input=lcd6.jpg --config=lcd6.config

generates an image where the actual sampling blocks used to decode the digits are filled in: lcd The red areas are the areas of the segments that are sampled. The green areas are scanned to determine what an 'off' segment would be measured as.

One of the advantages of this library is that any digit orientation is supported - the digits can be upside down, or even at an angle (which is useful if you have a large set of digits and you need to capture them in a diagonal direction to allow them to fit).

Image sources

The library uses the standard Go image package for processing the image to be decoded. The sample program uses static JPEG or PNG files as the source of the image for the overlay, which is fine for initial configuration and testing. However for real applications, the image should be separately sourced and supplied to the library. This is usually involves some form of webcam source. For example, the imageserver program that is part of the example programs from the webcam package is one that is easy to use.

Some of the utility programs (such as the calibrate program) can be supplied with a URL that is used to retrieve an appropriate image.

Calibration

A major part of the library is the dynamic level detection and setting so that segments are correctly recognised as 'on' or 'off', depending on the luminosity of the segments. There are a couple of significant factors at play here:

  1. Depending on the source of the image, conditions can change gradually because of changing light levels (e.g across the day). For best results, some form of lighting is recommended on the display being scanned, but even with this, ambient light changes mean that over time the 'on' and 'off' light levels may vary considerably, and unless this change taken into account, the accuracy of the decoding can be affected.
  2. Even with consistent lighting, the differences between 'on' and 'off' can vary widely across the complete image, so that levels that work with one digit may not work consistently with other digits in the same image.

To address this, each digit that is being processed in the image maintains its own set of calibrated levels that are used to detect when a segment is 'on' or 'off'. These levels are dynamically adjusted when it is determined a successful set of digit decoding has occurred, so that if light levels change, the threshold levels used to detect 'on' and 'off' are adjusted accordingly.

At regular intervals, these threshold levels are saved away in an internal database, along with a quality measurement, and a new set are chosen from this database. Poor quality levels are discarded. The best quality threshold levels are checkpointed to a file. These can be restored whenever a restart occurs, so that the library's decoding accuracy is consistent from the start.

Once a working set of high quality threshold limits is available, the library can handle widely varying light conditions whilst maintaining a high level of decoding accuracy. However, the challenge then becomes how to initially bootstrap this set of calibration values.

The library has a facility whereby a preset string of characters can be supplied along with an image that has the corresponding 7 segment characters, and the library will use this string of characters to build the 'on' and 'off' levels for all of the segments. So an inital set of images along with the corresponding character strings can be used to build an initial calibration database.

Another very useful tool is the calibrate program, which allows dynamic editing of the configuration defining the digit template and digit definitions, and also allows bootstrapping of the calibration levels through manual entry of the character strings.

For example:

./calibrate --config=sample.conf --calibration=/tmp/calibrate --train=false

will start a web server on port 8080, where images read from the image source are displayed optionally with the overlay digit markings. Changing the template or digit configuration in the sample.conf file will reload the configuration, displaying the overlay reflecting the changes. Running the program with --train=true will allow the manual entry of a character string, which is then used to dynamically adjust the calibration database, which is written to /tmp/calibration. The program attempts to decode the digits, and will display the decoded data. If it is correct, hitting enter without entering a string will use the decoded data as input to the calibration adjustment.

Examples

The most comprehensive example of the use of the library is MeterMan.

Disclaimer

This is not an officially supported Google product.

Documentation

Index

Constants

View Source
const (
	TL = iota
	TR = iota
	BR = iota
	BL = iota
)

Bounding box indices, representing the corners of the box (top/bottom left/right).

View Source
const (
	S_TL, M_TL = iota, 1 << iota // Top left
	S_TM, M_TM = iota, 1 << iota // Top middle
	S_TR, M_TR = iota, 1 << iota // Top right
	S_BR, M_BR = iota, 1 << iota // Bottom right
	S_BM, M_BM = iota, 1 << iota // Bottom middle
	S_BL, M_BL = iota, 1 << iota // Bottom left
	S_MM, M_MM = iota, 1 << iota // Middle
	SEGMENTS   = iota
)

Segments, as enum and bit mask.

Variables

This section is empty.

Functions

func DigitsToSegments

func DigitsToSegments(s string) ([]int, error)

Map each character in s to the bit mask representing the segments for that character.

func GetImage

func GetImage(src string) (image.Image, error)

Get an image from the source URL.

func ReadImage

func ReadImage(name string) (image.Image, error)

Read an image from a file.

func RotateImage

func RotateImage(image image.Image, angle float64) image.Image

Rotate the image, using a max sized canvas.

func SaveImage

func SaveImage(name string, img image.Image) error

Save the image, using the suffix to select the type of image.

Types

type Avg

type Avg struct {
	Value int
	// contains filtered or unexported fields
}

Avg represents a moving average of a value. A list of historical values is maintained and when new values are added, the moving average is recalculated.

func NewAvg

func NewAvg(size int) *Avg

Create a new moving average structure, size indicating the number of historical values to be kept.

func (*Avg) Add

func (m *Avg) Add(v int)

Add a new value to the history slice, drop the oldest, and recalculate the average.

func (*Avg) Copy

func (m *Avg) Copy() *Avg

Copy the moving average.

func (*Avg) Init

func (m *Avg) Init(v int)

Init the moving average with a default value.

func (*Avg) SetDefault

func (m *Avg) SetDefault(v int)

If not already initialised, init using this value.

type BBox

type BBox [4]Point

BBox represents a bounding box, with the indices above representing the corners.

func SegmentBB

func SegmentBB(s1, s2, e1, e2 Point, w, m int) BBox

Create a new bounding box representing one segment of a 7 segment digit. w represents the width of the segment, and m represents a margin that shrinks the box to ensure the box covers the bulk of the segment.

func (BBox) In

func (bb BBox) In(p Point) bool

In returns true if the point is in the bounding box.

func (BBox) Inner

func (bb BBox) Inner(m int) BBox

Copy the bounding box, shrinking the box by m pixels on each side to create an inner box.

func (BBox) Offset

func (bb BBox) Offset(x, y int) BBox

Create a new bounding box as a copy of bb, with the points offset by x and y.

func (BBox) Points

func (bb BBox) Points() PList

Return a list of all the points in the bounding box.

type DecodeResult

type DecodeResult struct {
	Img     image.Image    // Image that has been scanned
	Text    string         // Decoded string of digits
	Invalid int            // Count of invalid digits
	Scans   []*DigitScan   // Scan result
	Decodes []*DigitDecode // List of decoded digits
}

DecodeResult contains the results of scanning and decoding one image.

type Digit

type Digit struct {
	// contains filtered or unexported fields
}

Digit represents one 7-segment digit. It is typically created from a template, by offsetting the relative point values with the absolute point representing the top left of the digit. All point values are absolute as a result.

type DigitDecode

type DigitDecode struct {
	Char  byte   // The decoded character
	Str   string // The decoded char as a string
	Valid bool   // True if the decode was successful
	DP    bool   // True if the decimal point is set
}

DigitDecode is the result of decoding one digit in the image.

type DigitScan

type DigitScan struct {
	Segments []int // Averaged value for each segment
	DP       int   // Decimal point sample (if any)
	Mask     int   // Mask of segment bits
}

DigitScan contains the scanned values for one digit.

type LcdDecoder

type LcdDecoder struct {
	// Configuration values and flags.
	Threshold int  // Default on/off threshold
	History   int  // Size of moving average history
	MaxLevels int  // Maximum number of threshold levels
	Inverse   bool // True if darker is off e.g a LED rather than LCD.

	Digits []*Digit // List of digits to decode

	// Current calibration levels summary
	Best        int // Current highest quality
	Worst       int // Current lowest quality
	LastQuality int // Last quality level
	LastGood    int // Last count of good scans
	LastBad     int // Last count of bad scans
	Count       int // Count of levels
	Total       int // Sum of all qualities
	// contains filtered or unexported fields
}

LcdDecoder contains all the digit data required to decode the digits in an image.

func CreateLcdDecoder

func CreateLcdDecoder(conf config.Conf) (*LcdDecoder, error)

Create a 7 segment decoder using the configuration data provided.

func NewLcdDecoder

func NewLcdDecoder() *LcdDecoder

Create a new LcdDecoder.

func (*LcdDecoder) AddCalibration

func (l *LcdDecoder) AddCalibration(lev *levels)

Add a new calibration entry to the map

func (*LcdDecoder) AddDigit

func (l *LcdDecoder) AddDigit(name string, x, y int) (*Digit, error)

Add a digit using the named template. The template points are offset by the absolute point location of the digit (x, y).

func (*LcdDecoder) AddTemplate

func (l *LcdDecoder) AddTemplate(name string, bb []int, dp []int, width int) error

Add a digit template. Each template describes the parameters of one type/size of digit. bb contains a list of 3 points representing the top right, bottom right and bottom left of the boundaries of the digit. These are signed offsets from the implied base of (0,0) representing the top left of the digit. dp is an optional point offset where a decimal place is located. width is the width of the segment in pixels. All point references in the template are relative to the origin of the digit.

func (*LcdDecoder) Bad

func (l *LcdDecoder) Bad()

Record an unsuccessful decode.

func (*LcdDecoder) CalibrateUsingScan

func (l *LcdDecoder) CalibrateUsingScan(img image.Image, scans []*DigitScan) error

Adjust levels using scan result and segment bit masks.

func (*LcdDecoder) Decode

func (l *LcdDecoder) Decode(img image.Image) *DecodeResult

Decode the 7 segment digits in the image, and return a summary of the decoded values. curLevels must be initialised either by having the levels restored from a file, or having been calibrated with an image via Preset.

func (*LcdDecoder) DecodeErrors

func (l *LcdDecoder) DecodeErrors() []int

Return the digit decode error counters.

func (*LcdDecoder) GetCalibration

func (l *LcdDecoder) GetCalibration(qual int) (lev *levels)

Get one calibration entry from the map entry specified, removing it from the map. At least one element must exist in the map entry list.

func (*LcdDecoder) Good

func (l *LcdDecoder) Good()

Record a successful decode.

func (*LcdDecoder) MarkSamples

func (l *LcdDecoder) MarkSamples(img *image.RGBA, fill bool)

Mark the segments on this image. Draw white cross markers on the corners of the segments. If fill true, block fill the on and off portions of the segments.

func (*LcdDecoder) PickCalibration

func (l *LcdDecoder) PickCalibration()

Pick the best calibration from the list.

func (*LcdDecoder) Preset

func (l *LcdDecoder) Preset(img image.Image, digits string) error

Preset calculates the on and off threshold values from the image provided, using a preset result to map the on/off values for each segment.

func (*LcdDecoder) Recalibrate

func (l *LcdDecoder) Recalibrate()

Save the current levels calibration in the map, discard the worst, and pick the best.

func (*LcdDecoder) Restore

func (l *LcdDecoder) Restore(r io.Reader) (int, error)

Restore the calibration data from a saved cache. Format is a line of CSV, either:

index,quality
index,digit,segment,min,max

func (*LcdDecoder) RestoreFromFile

func (l *LcdDecoder) RestoreFromFile(f string) (int, error)

Restore the calibration data from a file.

func (*LcdDecoder) Save

func (l *LcdDecoder) Save(w io.WriteCloser, max int) error

Save the threshold data. Only the highest quality level sets are saved.

func (*LcdDecoder) SaveToFile

func (l *LcdDecoder) SaveToFile(f string, max int) error

Save the threshold data to a file.

func (*LcdDecoder) Scan

func (l *LcdDecoder) Scan(img image.Image) []*DigitScan

Scan samples the regions of the image that map to the segments of the digits, and returns a list of the scanned digits.

type PList

type PList []Point

List of points.

func Split

func Split(start, end Point, sections int) PList

Return a list of points that splits the line (identified by start and end) into a number of sections e.g if 3 sections are requested, a list of 2 points are returned, representing the points 1/3 and 2/3 along the line.

func (PList) Offset

func (p PList) Offset(x, y int) PList

Create a new point list from the point list, adding a x and y offset to each point.

func (PList) Print

func (pl PList) Print()

Print the point list.

type Point

type Point struct {
	X int
	Y int
}

func Adjust

func Adjust(s, e Point, adj int) Point

Return an adjusted point that is closer to e by the given amount.

func (Point) Block

func (p Point) Block(w int) PList

Create a new point list representing a square centered at p of width w.

func (Point) Offset

func (p Point) Offset(x, y int) Point

Return a new point offset from this point by x, y.

type Template

type Template struct {
	// contains filtered or unexported fields
}

Base template for one type/size of 7-segment digit. Points are all relative to the top left corner position. When a digit is created using this template, the points are offset from the point where the digit is placed. The idea is that different size of digits use a different template, and that multiple digits are created from a single template.

Directories

Path Synopsis
utils

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL