Number Plate Golf: Recognition See also: Parsing

Introduction

You're working on a government programming team, who have been programming the speed cameras. However, the group of people who have programmed the speed calculator have taken up too much space, so you have to make the number plate recognition software as small as possible.

Challenge

Given an image of a number plate, return the text on the plate.

Number plates

The following are all of the characters which your program must recognise:

ABCDEFG



H1JKLMN0



PQRSTUVW



XYZ01234



56789



Note

On British number plates, the characters for I (i) and 1 (one) are the same and the characters for O (o) and 0 (zero) are the same. For that reason, always assume characters are the numbers. I.e. the following number plate is 10 (one zero):



Examples

C0D3 GLF



B3T4 DCY



M1NUS 15



YET1CGN



Other rules

Internet access and OCR libraries and functions are disallowed.

The number plates will always look identical to the ones shown above. All number plates will be roughly same size (there will be some inaccuracies due to cropping method).

If you require lossless PNG versions of any number plates, I will supply them to you.

Scoring

The shortest program in bytes wins.

All number plates are screenshots of the search bar at this site
Mathematica 1170 1270 1096 1059 650 528 570 551 525 498 bytes

The latest version saves 27 bytes by not requiring that the plate be "trimmed" before it is parsed.  The penultimate version saved 26 bytes by using only 10 of the original 24 sample points. 

z=Partition;h@i_:=i~PixelValue~#/.{_,_,_,z_}:&gt;⌈z⌉&amp;/@z[{45,99,27,81,63,81,9,63,45,63,9,45,45,45,63,45,45,27,45,9},2];f@p_:=h/@SortBy[Select[p~ColorReplace~Yellow~ComponentMeasurements~{"Image","Centroid"},100&lt;Last@ImageDimensions@#[[2,1]]&lt;120&amp;],#[[2,2,1]]&amp;][[All,2,1]]/.Thread[IntegerDigits[#,2,10]&amp;/@(z[IntegerDigits[Subscript["ekqeuiv5pa5rsebjlic4i5886qsmvy34z5vu4e7nlg9qqe3g0p8hcioom6qrrkzv4k7c9fdc3shsm1cij7jrluo", "36"]],4]/.{a__Integer}:&gt; FromDigits[{a}])-&gt; Characters@"BD54TARP89Q0723Z6EFGCSWMNVYXHUJKL1"]




122 bytes saved through LegionMammal978's idea of packing the long list of base 10 numbers as a single, base 36 number.  He pared another 20 bytes off the final code.

The jump from 528 to 570 bytes was due to additional code to ensure that the order of the letters returned corresponded to the order of the letters on the license plate. The centroid for each letter contains the x-coordinate, which reveals the relative positions of the letters along x.



Ungolfed Code

coordinates=Flatten[Table[{x,y},{y,99,0,-18},{x,9,72,18}],1];
h[img_] :=ArrayReshape[PixelValue[img, #] /. {_, _, _, z_} :&gt;  ⌈z⌉  &amp; /@ coordinates, {6, 4}];
plateCrop[img_]:=ColorReplace[ImageTrim[img,{{100,53},{830,160}}],Yellow];
codes={{{15,13,15,13,13,15},"B"},{{15,8,8,8,9,15},"C"},{{15,13,13,13,13,15},"D"},{{15,8,14,8,8,15},"E"},{{15,8,14,8,8,8},"F"},{{15,8,8,11,9,15},"G"},{{6,6,6,6,15,9},"A"},{{9,9,15,15,9,9},"H"},{{8,8,8,8,8,15},"L"},{{9,15,15,15,13,9},"M"},{{15,9,9,9,9,15},"0"},{{9,10,12,14,10,9},"K"},{{9,13,13,11,11,9},"N"},{{8,8,8,8,8,8},"1"},{{1,1,1,1,9,15},"J"},{{15,9,15,14,8,8},"P"},{{15,9,9,9,15,15},"Q"},{{15,9,15,14,10,11},"R"},{{15,8,12,3,1,15},"S"},{{9,15,6,6,6,6},"V"},{{15,6,6,6,6,6},"T"},{{9,15,15,15,15,15},"W"},{{9,9,9,9,9,15},"U"},{{9,14,6,6,14,9},"X"},{{9,14,6,6,6,6},"Y"},{{15,3,2,4,12,15},"Z"},{{15,9,9,9,9,15},"0"},{{8,8,8,8,8,8},"1"},{{15,1,3,6,12,15},"2"},{{15,1,3,1,9,15},"3"},{{2,6,6,15,2,2},"4"},{{7,12,14,1,1,15},"5"},{{15,8,14,9,9,15},"6"},{{15,1,2,2,6,4},"7"},{{15,9,15,9,9,15},"8"},{{15,9,15,1,9,15},"9"}};
decryptRules=Rule@@@codes;
isolateLetters[img_]:=SortBy[Select[ComponentMeasurements[plateCrop[img],{"Image","Centroid"}],ImageDimensions[#[[2,1]]][[2]]&gt;100&amp;],#[[2,2,1]]&amp;][[All,2,1]]
f[plate_]:=FromDigits[#,2]&amp;/@#&amp;/@h/@isolateLetters[plate]/.decryptRules


Overview

The basic idea is to check whether a systematic sampling of pixels from the input image matches pixels from the same location on the bonafide images.  Much of the code consists of the bit signatures for each character,

The diagram shows the pixels that are sampled from the letters "J", "P","Q", and "R".



The pixel values can be represented as matrices. The dark, bold 1's correspond to black cells. The 0's correspond to white cells.



These are the decryption replacement rules for J  P  Q  R.

{1, 1, 1, 1, 9, 15} -> "J",
{15, 9, 15, 14, 8, 8} -> "P",
{15, 9, 9, 9, 15, 15} -> "Q",
{15, 9, 15, 14, 10, 11} -> "R"  

It should be possible to understand why the rule for "0" is:

{15, 9, 9, 9, 9, 15} -> "0"

and thus distinguishable from the letter "Q". 



The following shows the 10 points used in the final version.  These points are sufficient for identifying all of the characters.





What the functions do

plateCrop[img] removes the frame and left edge from the plate, makes the background white.  I was able to eliminate this function from the final version by selecting image components, possible letters  that were between 100 and 120 pixels high.





isolateLetters[img] removes the individual letters from the cropped image.

We can display how it works by showing where the cropped image, output from plateCrop goes as input for isolateLetters. The output is a list of individual characters.  





Coordinates are 24 evenly distributed positions for checking the pixel color. The coordinates correspond to those in the first figure.

coordinates=Flatten[Table[{x,y},{y,99,0,-18},{x,9,72,18}],1];


{{9, 99}, {27, 99}, {45, 99}, {63, 99}, {9, 81}, {27, 81}, {45, 
  81}, {63, 81}, {9, 63}, {27, 63}, {45, 63}, {63, 63}, {9, 45}, {27, 
  45}, {45, 45}, {63, 45}, {9, 27}, {27, 27}, {45, 27}, {63, 27}, {9, 
  9}, {27, 9}, {45, 9}, {63, 9}}



h converts the pixels to binary.

h[img_] :=ArrayReshape[PixelValue[img, #] /. {_, _, _, z_} :&gt;  ⌈z⌉  &amp; /@ coordinates, {6, 4}];




codes are the signature for each character.  The decimal values are abbreviations of the binary code for black (0) and White (1) cells. In the golfed version, base 36 is used.

codes={{{15, 9, 9, 9, 9, 15}, "0"}, {{8, 8, 8, 8, 8, 8}, "1"}, {{15, 1, 3,6,12, 15}, "2"}, {{15, 1, 3, 1, 9, 15}, "3"}, {{2, 6, 6, 15, 2, 2}, "4"}, {{7, 12, 14, 1, 1, 15},"5"}, {{15, 8, 14, 9, 9, 15}, "6"}, {{15, 1, 2, 2, 6, 4},"7"}, {{15, 9, 15, 9, 9, 15}, "8"}, {{15, 9, 15, 1, 9, 15},"9"}, {{6, 6, 6, 6, 15, 9}, "A"}, {{15, 13, 15, 13, 13, 15}, "B"}, {{15, 8, 8, 8, 9, 15}, "C"}, {{15, 13, 13, 13, 13, 15}, "D"}, {{15, 8, 14, 8, 8, 15}, "E"}, {{15, 8, 14, 8, 8, 8},"F"}, {{15, 8, 8, 11, 9, 15}, "G"}, {{9, 9, 15, 15, 9, 9}, "H"}, {{1, 1, 1, 1, 9, 15}, "J"}, {{9, 10, 12, 14, 10, 9}, "K"}, {{8, 8, 8, 8, 8, 15}, "L"}, {{9, 15, 15, 15, 13, 9}, "M"}, {{9, 13, 13, 11, 11, 9}, "N"}, {{15, 9, 15, 14, 8, 8}, "P"}, {{15, 9, 9, 9, 15, 15}, "Q"}, {{15, 9, 15, 14, 10, 11}, "R"}, {{15, 8, 12, 3, 1, 15}, "S"}, {{15, 6, 6, 6, 6, 6}, "T"}, {{9, 9, 9, 9, 9, 15}, "U"}, {{9, 15, 6, 6, 6, 6}, "V"}, {{9, 15, 15, 15, 15, 15}, "W"}, {{9, 14, 6, 6, 14, 9}, "X"}, {{9, 14, 6, 6, 6, 6}, "Y"}, {{15, 3, 2, 4, 12, 15}, "Z"}};


(* decryptRules are for replacing signatures with their respective character *)

decryptRules=Rule@@@codes;


f is the function that takes an image of a license plate and returns a letter. 

f[plate_]:=FromDigits[#,2]&amp;/@#&amp;/@h/@isolate[plateCrop@plate]/.decryptRules;




{"A", "B", "C", "D", "E", "F", "G"}
{"H", "1", "J", "K", "L", "M", "N", "0"}
{"P", "Q", "R", "S", "T", "U", "V", "W"}
{"X", "Y", "Z", "0", "1", "2", "3", "4"}
{"5", "6", "7", "8", "9"}



Golfed

The code is shortened by using a single decimal number to represent all 24 bits (white or black) for each character. For example, the letter "J" uses the following replacement rule: 1118623 -&gt; "J".

1118623 corresponds to  

IntegerDigits[1118623 , 2, 24]


{0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1,
  1}

which can be repackaged as

ArrayReshape[{0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1}, {6, 4}]


{{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {1, 0, 0, 
  1}, {1, 1, 1, 1}}

which is simply the matrix for "J" that we saw above.

%//MatrixForm




Another savings comes from representing the alphabet as "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ" rather than as a list of letters.

Finally, all of the functions from the long version, except h, were integrated into the function f rather than defined separately.



h@i_:=ArrayReshape[i~PixelValue~#/.{_,_,_,z_}:&gt;⌈z⌉&amp;/@Join@@Table[{x,y},{y,99,0,-18},{x,9,72,18}],{6,4}];f@p_:=#~FromDigits~2&amp;/@(Join@@@h/@SortBy[Select[p~ImageTrim~{{100,53},{830,160}}~ColorReplace~Yellow~ComponentMeasurements~{"Image","Centroid"},Last@ImageDimensions@#[[2,1]]&gt;100&amp;],#[[2,2,1]]&amp;][[;;,2,1]])/.Thread[IntegerDigits[36^^1c01agxiuxom9ds3c3cskcp0esglxf68g235g1d27jethy2e1lbttwk1xj6yf590oin0ny1r45wc1i6yu68zxnm2jnb8vkkjc5yu06t05l0xnqhw9oi2lwvzd5f6lsvsb4izs1kse3xvx694zwxz007pnj8f6n,8^8]-&gt;Characters@"J4A51LUHKNYXVMW732ZTCGSFE60Q98PRDB"]