Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Plunger/barCodeSensor.h@86:e30a1f60f783, 2017-04-21 (annotated)
- Committer:
- mjr
- Date:
- Fri Apr 21 18:50:37 2017 +0000
- Revision:
- 86:e30a1f60f783
- Parent:
- 82:4f6209cb5c33
- Child:
- 87:8d35c74403af
Capture a bunch of alternative bar code decoder tests, mostly unsuccessful
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 82:4f6209cb5c33 | 1 | // Plunger sensor type for bar-code based absolute position encoders. |
mjr | 82:4f6209cb5c33 | 2 | // This type of sensor uses an optical sensor that moves with the plunger |
mjr | 82:4f6209cb5c33 | 3 | // along a guide rail with printed bar codes along its length that encode |
mjr | 82:4f6209cb5c33 | 4 | // the absolute position at each point. We figure the plunger position |
mjr | 82:4f6209cb5c33 | 5 | // by reading the bar code and decoding it into a position figure. |
mjr | 82:4f6209cb5c33 | 6 | // |
mjr | 82:4f6209cb5c33 | 7 | // The bar code has to be encoded in a specific format that we recognize. |
mjr | 82:4f6209cb5c33 | 8 | // We use a 10-bit reflected Gray code, optically encoded using a Manchester- |
mjr | 82:4f6209cb5c33 | 9 | // type of coding. Each bit is represented as a fixed-width area on the |
mjr | 82:4f6209cb5c33 | 10 | // bar, half white and half black. The bit value is encoded in the order |
mjr | 82:4f6209cb5c33 | 11 | // of the colors: Black/White is '0', and White/Black is '1'. |
mjr | 82:4f6209cb5c33 | 12 | // |
mjr | 86:e30a1f60f783 | 13 | // Gray codes are ideal for this type of application. Gray codes are |
mjr | 86:e30a1f60f783 | 14 | // defined such that each code point differs in exactly one bit from each |
mjr | 86:e30a1f60f783 | 15 | // adjacent code point. This provides natural error correction when used |
mjr | 86:e30a1f60f783 | 16 | // as a position scale, since any single-bit error will yield a code point |
mjr | 86:e30a1f60f783 | 17 | // reading that's only one spot off from the true position. So a bit read |
mjr | 86:e30a1f60f783 | 18 | // error acts like a reduction in precision. Likewise, any time the sensor |
mjr | 86:e30a1f60f783 | 19 | // is halfway between two code points, only one bit will be ambiguous, so |
mjr | 86:e30a1f60f783 | 20 | // the reading will come out as one of points on either side of the true |
mjr | 86:e30a1f60f783 | 21 | // position. Finally, motion blur will have the same effect, of creating |
mjr | 86:e30a1f60f783 | 22 | // ambiguity in the least significant bits, and thus giving us a reading |
mjr | 86:e30a1f60f783 | 23 | // that's correct to as many bits as we can read with teh blur. |
mjr | 82:4f6209cb5c33 | 24 | // |
mjr | 82:4f6209cb5c33 | 25 | // We use the Manchester-type optical coding because it has good properties |
mjr | 86:e30a1f60f783 | 26 | // for low-contrast images, and doesn't require uniform lighting. Each bit's |
mjr | 86:e30a1f60f783 | 27 | // pixel span contains equal numbers of light and dark pixels, so each bit |
mjr | 86:e30a1f60f783 | 28 | // provides its own local level reference. This means we don't care about |
mjr | 86:e30a1f60f783 | 29 | // lighting uniformity over the whole image, because we don't need a global |
mjr | 86:e30a1f60f783 | 30 | // notion of light and dark, just a local one over a single bit at a time. |
mjr | 82:4f6209cb5c33 | 31 | // |
mjr | 82:4f6209cb5c33 | 32 | |
mjr | 82:4f6209cb5c33 | 33 | #ifndef _BARCODESENSOR_H_ |
mjr | 82:4f6209cb5c33 | 34 | #define _BARCODESENSOR_H_ |
mjr | 82:4f6209cb5c33 | 35 | |
mjr | 82:4f6209cb5c33 | 36 | #include "plunger.h" |
mjr | 82:4f6209cb5c33 | 37 | #include "tsl14xxSensor.h" |
mjr | 82:4f6209cb5c33 | 38 | |
mjr | 82:4f6209cb5c33 | 39 | // Base class for bar-code sensors |
mjr | 86:e30a1f60f783 | 40 | // |
mjr | 86:e30a1f60f783 | 41 | // This is a template class with template parameters for the bar |
mjr | 86:e30a1f60f783 | 42 | // code pixel structure. The bar code layout is fixed for a given |
mjr | 86:e30a1f60f783 | 43 | // sensor type. We can assume fixed pixel sizes because we don't |
mjr | 86:e30a1f60f783 | 44 | // have to process arbitrary images. We only have to read scales |
mjr | 86:e30a1f60f783 | 45 | // specially prepared for this application, so we can count on them |
mjr | 86:e30a1f60f783 | 46 | // being printed at an exact size relative to the sensor pixels. |
mjr | 86:e30a1f60f783 | 47 | // |
mjr | 86:e30a1f60f783 | 48 | // nBits = Number of bits in the code |
mjr | 86:e30a1f60f783 | 49 | // |
mjr | 86:e30a1f60f783 | 50 | // leftBarWidth = Width in pixels of delimiting left bar. The code is |
mjr | 86:e30a1f60f783 | 51 | // delimited by a black bar on the "left" end, nearest pixel 0. This |
mjr | 86:e30a1f60f783 | 52 | // gives the pixel width of the bar. |
mjr | 86:e30a1f60f783 | 53 | // |
mjr | 86:e30a1f60f783 | 54 | // leftBarMaxOfs = Maximum offset of the delimiting bar from the left |
mjr | 86:e30a1f60f783 | 55 | // edge of the sensor (pixel 0), in pixels |
mjr | 86:e30a1f60f783 | 56 | // |
mjr | 86:e30a1f60f783 | 57 | // bitWidth = Width of each bit in pixels. This is the width of the |
mjr | 86:e30a1f60f783 | 58 | // full bit, including both "half bits" - it's the full white/black or |
mjr | 86:e30a1f60f783 | 59 | // black/white pattern area. |
mjr | 86:e30a1f60f783 | 60 | |
mjr | 86:e30a1f60f783 | 61 | template <int nBits, int leftBarWidth, int leftBarMaxOfs, int bitWidth> |
mjr | 82:4f6209cb5c33 | 62 | class PlungerSensorBarCode |
mjr | 82:4f6209cb5c33 | 63 | { |
mjr | 82:4f6209cb5c33 | 64 | public: |
mjr | 86:e30a1f60f783 | 65 | // process the image |
mjr | 82:4f6209cb5c33 | 66 | bool process(const uint8_t *pix, int npix, int &pos) |
mjr | 82:4f6209cb5c33 | 67 | { |
mjr | 86:e30a1f60f783 | 68 | #if 0 // $$$ |
mjr | 86:e30a1f60f783 | 69 | |
mjr | 86:e30a1f60f783 | 70 | // scan from the left edge until we find the fixed '0' start bit |
mjr | 86:e30a1f60f783 | 71 | for (int i = 0 ; i < leftBarMaxOfs ; ++i, ++pix) |
mjr | 86:e30a1f60f783 | 72 | { |
mjr | 86:e30a1f60f783 | 73 | // check for the '0' bit |
mjr | 86:e30a1f60f783 | 74 | if (readBit8(pix) == 0) |
mjr | 86:e30a1f60f783 | 75 | { |
mjr | 86:e30a1f60f783 | 76 | // got it - skip the start bit |
mjr | 86:e30a1f60f783 | 77 | pix += bitWidth; |
mjr | 86:e30a1f60f783 | 78 | |
mjr | 86:e30a1f60f783 | 79 | // read the gray code bits |
mjr | 86:e30a1f60f783 | 80 | int gray = 0; |
mjr | 86:e30a1f60f783 | 81 | for (int j = 0 ; j < nBits ; ++j, pix += bitWidth) |
mjr | 86:e30a1f60f783 | 82 | { |
mjr | 86:e30a1f60f783 | 83 | // read the bit; return failure if we can't decode a bit |
mjr | 86:e30a1f60f783 | 84 | int bit = readBit8(pix); |
mjr | 86:e30a1f60f783 | 85 | if (bit < 0) |
mjr | 86:e30a1f60f783 | 86 | return false; |
mjr | 86:e30a1f60f783 | 87 | |
mjr | 86:e30a1f60f783 | 88 | // shift it into the code |
mjr | 86:e30a1f60f783 | 89 | gray = (gray << 1) | bit; |
mjr | 86:e30a1f60f783 | 90 | } |
mjr | 86:e30a1f60f783 | 91 | } |
mjr | 86:e30a1f60f783 | 92 | |
mjr | 86:e30a1f60f783 | 93 | // convert the gray code to binary |
mjr | 86:e30a1f60f783 | 94 | int bin = grayToBin(gray); |
mjr | 86:e30a1f60f783 | 95 | |
mjr | 86:e30a1f60f783 | 96 | // compute the parity of the binary value |
mjr | 86:e30a1f60f783 | 97 | int parity = 0; |
mjr | 86:e30a1f60f783 | 98 | for (int j = 0 ; j < nBits ; ++j) |
mjr | 86:e30a1f60f783 | 99 | parity ^= bin >> j; |
mjr | 86:e30a1f60f783 | 100 | |
mjr | 86:e30a1f60f783 | 101 | // figure the bit required for odd parity |
mjr | 86:e30a1f60f783 | 102 | int odd = (parity & 0x01) ^ 0x01; |
mjr | 86:e30a1f60f783 | 103 | |
mjr | 86:e30a1f60f783 | 104 | // read the check bit |
mjr | 86:e30a1f60f783 | 105 | int bit = readBit8(pix); |
mjr | 86:e30a1f60f783 | 106 | if (pix < 0) |
mjr | 86:e30a1f60f783 | 107 | return false; |
mjr | 86:e30a1f60f783 | 108 | |
mjr | 86:e30a1f60f783 | 109 | // check that it matches the expected parity |
mjr | 86:e30a1f60f783 | 110 | if (bit != odd) |
mjr | 86:e30a1f60f783 | 111 | return false; |
mjr | 86:e30a1f60f783 | 112 | |
mjr | 86:e30a1f60f783 | 113 | // success |
mjr | 86:e30a1f60f783 | 114 | pos = bin; |
mjr | 86:e30a1f60f783 | 115 | return true; |
mjr | 86:e30a1f60f783 | 116 | } |
mjr | 86:e30a1f60f783 | 117 | |
mjr | 86:e30a1f60f783 | 118 | // no code found |
mjr | 82:4f6209cb5c33 | 119 | return false; |
mjr | 86:e30a1f60f783 | 120 | |
mjr | 86:e30a1f60f783 | 121 | #else |
mjr | 86:e30a1f60f783 | 122 | int barStart = leftBarMaxOfs/2; |
mjr | 86:e30a1f60f783 | 123 | if (leftBarWidth != 0) // $$$ |
mjr | 86:e30a1f60f783 | 124 | { |
mjr | 86:e30a1f60f783 | 125 | // Find the black bar on the left side (nearest pixel 0) that |
mjr | 86:e30a1f60f783 | 126 | // delimits the start of the bar code. To find it, first figure |
mjr | 86:e30a1f60f783 | 127 | // the average brightness over the left margin up to the maximum |
mjr | 86:e30a1f60f783 | 128 | // allowable offset, then look for the bar by finding the first |
mjr | 86:e30a1f60f783 | 129 | // bar-width run of pixels that are darker than the average. |
mjr | 86:e30a1f60f783 | 130 | int lsum = 0; |
mjr | 86:e30a1f60f783 | 131 | for (int x = 1 ; x <= leftBarMaxOfs ; ++x) |
mjr | 86:e30a1f60f783 | 132 | lsum += pix[x]; |
mjr | 86:e30a1f60f783 | 133 | int lavg = lsum / leftBarMaxOfs; |
mjr | 86:e30a1f60f783 | 134 | |
mjr | 86:e30a1f60f783 | 135 | // now find the first dark edge |
mjr | 86:e30a1f60f783 | 136 | for (int x = 0 ; x < leftBarMaxOfs ; ++x) |
mjr | 86:e30a1f60f783 | 137 | { |
mjr | 86:e30a1f60f783 | 138 | // if this pixel is dark, and one of the next two is dark, |
mjr | 86:e30a1f60f783 | 139 | // take it as the edge |
mjr | 86:e30a1f60f783 | 140 | if (pix[x] < lavg && (pix[x+1] < lavg || pix[x+2] < lavg)) |
mjr | 86:e30a1f60f783 | 141 | { |
mjr | 86:e30a1f60f783 | 142 | // move past the delimier |
mjr | 86:e30a1f60f783 | 143 | barStart = x + leftBarWidth; |
mjr | 86:e30a1f60f783 | 144 | break; |
mjr | 86:e30a1f60f783 | 145 | } |
mjr | 86:e30a1f60f783 | 146 | } |
mjr | 86:e30a1f60f783 | 147 | } |
mjr | 86:e30a1f60f783 | 148 | else |
mjr | 86:e30a1f60f783 | 149 | { |
mjr | 86:e30a1f60f783 | 150 | barStart = 4; // $$$ should be configurable via config tool |
mjr | 86:e30a1f60f783 | 151 | } |
mjr | 86:e30a1f60f783 | 152 | |
mjr | 86:e30a1f60f783 | 153 | // Scan the bits |
mjr | 86:e30a1f60f783 | 154 | int barcode = 0; |
mjr | 86:e30a1f60f783 | 155 | for (int bit = 0, x0 = barStart; bit < nBits ; ++bit, x0 += bitWidth) |
mjr | 86:e30a1f60f783 | 156 | { |
mjr | 86:e30a1f60f783 | 157 | // figure the extent of this bit |
mjr | 86:e30a1f60f783 | 158 | int x1 = x0 + bitWidth / 2; |
mjr | 86:e30a1f60f783 | 159 | int x2 = x0 + bitWidth; |
mjr | 86:e30a1f60f783 | 160 | if (x1 > npix) x1 = npix; |
mjr | 86:e30a1f60f783 | 161 | if (x2 > npix) x2 = npix; |
mjr | 86:e30a1f60f783 | 162 | |
mjr | 86:e30a1f60f783 | 163 | // get the average of the pixels over the bit |
mjr | 86:e30a1f60f783 | 164 | int sum = 0; |
mjr | 86:e30a1f60f783 | 165 | for (int x = x0 ; x < x2 ; ++x) |
mjr | 86:e30a1f60f783 | 166 | sum += pix[x]; |
mjr | 86:e30a1f60f783 | 167 | int avg = sum / bitWidth; |
mjr | 86:e30a1f60f783 | 168 | |
mjr | 86:e30a1f60f783 | 169 | // Scan the left and right sections. Classify each |
mjr | 86:e30a1f60f783 | 170 | // section according to whether the majority of its |
mjr | 86:e30a1f60f783 | 171 | // pixels are above or below the local average. |
mjr | 86:e30a1f60f783 | 172 | int lsum = 0, rsum = 0; |
mjr | 86:e30a1f60f783 | 173 | for (int x = x0 + 1 ; x < x1 - 1 ; ++x) |
mjr | 86:e30a1f60f783 | 174 | lsum += (pix[x] < avg ? 0 : 1); |
mjr | 86:e30a1f60f783 | 175 | for (int x = x1 + 1 ; x < x2 - 1 ; ++x) |
mjr | 86:e30a1f60f783 | 176 | rsum += (pix[x] < avg ? 0 : 1); |
mjr | 86:e30a1f60f783 | 177 | |
mjr | 86:e30a1f60f783 | 178 | // if we don't have a winner, fail |
mjr | 86:e30a1f60f783 | 179 | if (lsum == rsum) |
mjr | 86:e30a1f60f783 | 180 | return false; |
mjr | 86:e30a1f60f783 | 181 | |
mjr | 86:e30a1f60f783 | 182 | // black/white = 0, white/black = 1 |
mjr | 86:e30a1f60f783 | 183 | barcode = (barcode << 1) | (lsum < rsum ? 0 : 1); |
mjr | 86:e30a1f60f783 | 184 | } |
mjr | 86:e30a1f60f783 | 185 | |
mjr | 86:e30a1f60f783 | 186 | // decode the Gray code value to binary |
mjr | 86:e30a1f60f783 | 187 | pos = grayToBin(barcode); |
mjr | 86:e30a1f60f783 | 188 | |
mjr | 86:e30a1f60f783 | 189 | // success |
mjr | 86:e30a1f60f783 | 190 | return true; |
mjr | 86:e30a1f60f783 | 191 | #endif |
mjr | 86:e30a1f60f783 | 192 | } |
mjr | 86:e30a1f60f783 | 193 | |
mjr | 86:e30a1f60f783 | 194 | // read a bar starting at the given pixel |
mjr | 86:e30a1f60f783 | 195 | int readBit8(const uint8_t *pix) |
mjr | 86:e30a1f60f783 | 196 | { |
mjr | 86:e30a1f60f783 | 197 | // pull out the pixels for the bar |
mjr | 86:e30a1f60f783 | 198 | uint8_t s[8]; |
mjr | 86:e30a1f60f783 | 199 | memcpy(s, pix, 8); |
mjr | 86:e30a1f60f783 | 200 | |
mjr | 86:e30a1f60f783 | 201 | // sort them in brightness order (using an 8-element network sort) |
mjr | 86:e30a1f60f783 | 202 | #define SWAP(a, b) if (s[a] > s[b]) { uint8_t tmp = s[a]; s[a] = s[b]; s[b] = tmp; } |
mjr | 86:e30a1f60f783 | 203 | SWAP(0, 1); |
mjr | 86:e30a1f60f783 | 204 | SWAP(2, 3); |
mjr | 86:e30a1f60f783 | 205 | SWAP(0, 2); |
mjr | 86:e30a1f60f783 | 206 | SWAP(1, 3); |
mjr | 86:e30a1f60f783 | 207 | SWAP(1, 2); |
mjr | 86:e30a1f60f783 | 208 | SWAP(4, 5); |
mjr | 86:e30a1f60f783 | 209 | SWAP(6, 7); |
mjr | 86:e30a1f60f783 | 210 | SWAP(4, 6); |
mjr | 86:e30a1f60f783 | 211 | SWAP(5, 7); |
mjr | 86:e30a1f60f783 | 212 | SWAP(5, 6); |
mjr | 86:e30a1f60f783 | 213 | SWAP(0, 4); |
mjr | 86:e30a1f60f783 | 214 | SWAP(1, 5); |
mjr | 86:e30a1f60f783 | 215 | SWAP(1, 4); |
mjr | 86:e30a1f60f783 | 216 | SWAP(2, 6); |
mjr | 86:e30a1f60f783 | 217 | SWAP(3, 7); |
mjr | 86:e30a1f60f783 | 218 | SWAP(3, 6); |
mjr | 86:e30a1f60f783 | 219 | SWAP(2, 4); |
mjr | 86:e30a1f60f783 | 220 | SWAP(3, 5); |
mjr | 86:e30a1f60f783 | 221 | SWAP(3, 4); |
mjr | 86:e30a1f60f783 | 222 | #undef SWAP |
mjr | 86:e30a1f60f783 | 223 | |
mjr | 86:e30a1f60f783 | 224 | // figure the median brightness |
mjr | 86:e30a1f60f783 | 225 | int median = (int(s[3]) + s[4] + 1) / 2; |
mjr | 86:e30a1f60f783 | 226 | |
mjr | 86:e30a1f60f783 | 227 | // count pixels below the median on each side |
mjr | 86:e30a1f60f783 | 228 | int ldark = 0, rdark = 0; |
mjr | 86:e30a1f60f783 | 229 | for (int i = 0 ; i < 3 ; ++i) |
mjr | 86:e30a1f60f783 | 230 | { |
mjr | 86:e30a1f60f783 | 231 | if (pix[i] < median) |
mjr | 86:e30a1f60f783 | 232 | ldark++; |
mjr | 86:e30a1f60f783 | 233 | } |
mjr | 86:e30a1f60f783 | 234 | for (int i = 4 ; i < 8 ; ++i) |
mjr | 86:e30a1f60f783 | 235 | { |
mjr | 86:e30a1f60f783 | 236 | if (pix[i] < median) |
mjr | 86:e30a1f60f783 | 237 | rdark++; |
mjr | 86:e30a1f60f783 | 238 | } |
mjr | 86:e30a1f60f783 | 239 | |
mjr | 86:e30a1f60f783 | 240 | // we need >=3 dark + >=3 light or vice versa |
mjr | 86:e30a1f60f783 | 241 | if (ldark >= 3 && rdark <= 1) |
mjr | 86:e30a1f60f783 | 242 | { |
mjr | 86:e30a1f60f783 | 243 | // dark + light = '0' bit |
mjr | 86:e30a1f60f783 | 244 | return 0; |
mjr | 86:e30a1f60f783 | 245 | } |
mjr | 86:e30a1f60f783 | 246 | if (ldark <= 1 && rdark >= 3) |
mjr | 86:e30a1f60f783 | 247 | { |
mjr | 86:e30a1f60f783 | 248 | // light + dark = '1' bit |
mjr | 86:e30a1f60f783 | 249 | return 1; |
mjr | 86:e30a1f60f783 | 250 | } |
mjr | 86:e30a1f60f783 | 251 | else |
mjr | 86:e30a1f60f783 | 252 | { |
mjr | 86:e30a1f60f783 | 253 | // ambiguous bit |
mjr | 86:e30a1f60f783 | 254 | return -1; |
mjr | 86:e30a1f60f783 | 255 | } |
mjr | 86:e30a1f60f783 | 256 | } |
mjr | 86:e30a1f60f783 | 257 | |
mjr | 86:e30a1f60f783 | 258 | // convert a reflected Gray code value (up to 16 bits) to binary |
mjr | 86:e30a1f60f783 | 259 | int grayToBin(int grayval) |
mjr | 86:e30a1f60f783 | 260 | { |
mjr | 86:e30a1f60f783 | 261 | int temp = grayval ^ (grayval >> 8); |
mjr | 86:e30a1f60f783 | 262 | temp ^= (temp >> 4); |
mjr | 86:e30a1f60f783 | 263 | temp ^= (temp >> 2); |
mjr | 86:e30a1f60f783 | 264 | temp ^= (temp >> 1); |
mjr | 86:e30a1f60f783 | 265 | return temp; |
mjr | 82:4f6209cb5c33 | 266 | } |
mjr | 82:4f6209cb5c33 | 267 | }; |
mjr | 82:4f6209cb5c33 | 268 | |
mjr | 86:e30a1f60f783 | 269 | // Auto-exposure counter |
mjr | 86:e30a1f60f783 | 270 | class BarCodeExposureCounter |
mjr | 86:e30a1f60f783 | 271 | { |
mjr | 86:e30a1f60f783 | 272 | public: |
mjr | 86:e30a1f60f783 | 273 | BarCodeExposureCounter() |
mjr | 86:e30a1f60f783 | 274 | { |
mjr | 86:e30a1f60f783 | 275 | nDark = 0; |
mjr | 86:e30a1f60f783 | 276 | nBright = 0; |
mjr | 86:e30a1f60f783 | 277 | nZero = 0; |
mjr | 86:e30a1f60f783 | 278 | nSat = 0; |
mjr | 86:e30a1f60f783 | 279 | } |
mjr | 86:e30a1f60f783 | 280 | |
mjr | 86:e30a1f60f783 | 281 | inline void count(int pix) |
mjr | 86:e30a1f60f783 | 282 | { |
mjr | 86:e30a1f60f783 | 283 | if (pix <= 2) |
mjr | 86:e30a1f60f783 | 284 | ++nZero; |
mjr | 86:e30a1f60f783 | 285 | else if (pix < 12) |
mjr | 86:e30a1f60f783 | 286 | ++nDark; |
mjr | 86:e30a1f60f783 | 287 | else if (pix >= 253) |
mjr | 86:e30a1f60f783 | 288 | ++nSat; |
mjr | 86:e30a1f60f783 | 289 | else if (pix > 200) |
mjr | 86:e30a1f60f783 | 290 | ++nBright; |
mjr | 86:e30a1f60f783 | 291 | } |
mjr | 86:e30a1f60f783 | 292 | |
mjr | 86:e30a1f60f783 | 293 | int nDark; // dark pixels |
mjr | 86:e30a1f60f783 | 294 | int nBright; // bright pixels |
mjr | 86:e30a1f60f783 | 295 | int nZero; // pixels at zero brightness |
mjr | 86:e30a1f60f783 | 296 | int nSat; // pixels at full saturation |
mjr | 86:e30a1f60f783 | 297 | }; |
mjr | 86:e30a1f60f783 | 298 | |
mjr | 86:e30a1f60f783 | 299 | // PlungerSensor interface implementation for bar code readers. |
mjr | 86:e30a1f60f783 | 300 | // |
mjr | 86:e30a1f60f783 | 301 | // Bar code readers are image sensors, so we have a pixel size for |
mjr | 86:e30a1f60f783 | 302 | // the sensor. However, this isn't the scale for the readings. The |
mjr | 86:e30a1f60f783 | 303 | // scale for the readings is determined by the number of bits in the |
mjr | 86:e30a1f60f783 | 304 | // bar code, since an n-bit bar code can encode 2^n distinct positions. |
mjr | 86:e30a1f60f783 | 305 | // |
mjr | 86:e30a1f60f783 | 306 | template <int nBits, int leftBarWidth, int leftBarMaxOfs, int bitWidth> |
mjr | 86:e30a1f60f783 | 307 | class PlungerSensorBarCodeTSL14xx: public PlungerSensorTSL14xxSmall, |
mjr | 86:e30a1f60f783 | 308 | PlungerSensorBarCode<nBits, leftBarWidth, leftBarMaxOfs, bitWidth> |
mjr | 82:4f6209cb5c33 | 309 | { |
mjr | 82:4f6209cb5c33 | 310 | public: |
mjr | 82:4f6209cb5c33 | 311 | PlungerSensorBarCodeTSL14xx(int nativePix, PinName si, PinName clock, PinName ao) |
mjr | 86:e30a1f60f783 | 312 | : PlungerSensorTSL14xxSmall(nativePix, (1 << nBits) - 1, si, clock, ao) |
mjr | 82:4f6209cb5c33 | 313 | { |
mjr | 86:e30a1f60f783 | 314 | // the native scale is the number of positions we can |
mjr | 86:e30a1f60f783 | 315 | // encode in the bar code |
mjr | 86:e30a1f60f783 | 316 | nativeScale = 1023; |
mjr | 82:4f6209cb5c33 | 317 | } |
mjr | 82:4f6209cb5c33 | 318 | |
mjr | 82:4f6209cb5c33 | 319 | protected: |
mjr | 86:e30a1f60f783 | 320 | |
mjr | 82:4f6209cb5c33 | 321 | // process the image through the bar code reader |
mjr | 82:4f6209cb5c33 | 322 | virtual bool process(const uint8_t *pix, int npix, int &pos) |
mjr | 82:4f6209cb5c33 | 323 | { |
mjr | 82:4f6209cb5c33 | 324 | // adjust the exposure |
mjr | 82:4f6209cb5c33 | 325 | adjustExposure(pix, npix); |
mjr | 82:4f6209cb5c33 | 326 | |
mjr | 82:4f6209cb5c33 | 327 | // do the standard bar code processing |
mjr | 86:e30a1f60f783 | 328 | return PlungerSensorBarCode<nBits, leftBarWidth, leftBarMaxOfs, bitWidth> |
mjr | 86:e30a1f60f783 | 329 | ::process(pix, npix, pos); |
mjr | 82:4f6209cb5c33 | 330 | } |
mjr | 82:4f6209cb5c33 | 331 | |
mjr | 86:e30a1f60f783 | 332 | // bar code sensor orientation is fixed |
mjr | 86:e30a1f60f783 | 333 | virtual int getOrientation() const { return 1; } |
mjr | 86:e30a1f60f783 | 334 | |
mjr | 82:4f6209cb5c33 | 335 | // adjust the exposure |
mjr | 82:4f6209cb5c33 | 336 | void adjustExposure(const uint8_t *pix, int npix) |
mjr | 82:4f6209cb5c33 | 337 | { |
mjr | 86:e30a1f60f783 | 338 | #if 1 |
mjr | 86:e30a1f60f783 | 339 | // The Manchester code has a nice property for auto exposure |
mjr | 86:e30a1f60f783 | 340 | // control: each bit area has equal numbers of white and black |
mjr | 86:e30a1f60f783 | 341 | // pixels. So we know exactly how the overall population of |
mjr | 86:e30a1f60f783 | 342 | // pixels has to look: the bit area will be 50% black and 50% |
mjr | 86:e30a1f60f783 | 343 | // white, and the margins will be all white. For maximum |
mjr | 86:e30a1f60f783 | 344 | // contrast, target an exposure level where the black pixels |
mjr | 86:e30a1f60f783 | 345 | // are all below the middle brightness level and the white |
mjr | 86:e30a1f60f783 | 346 | // pixels are all above. Start by figuring the number of |
mjr | 86:e30a1f60f783 | 347 | // pixels above and below. |
mjr | 86:e30a1f60f783 | 348 | int nDark = 0; |
mjr | 86:e30a1f60f783 | 349 | for (int i = 0 ; i < npix ; ++i) |
mjr | 86:e30a1f60f783 | 350 | { |
mjr | 86:e30a1f60f783 | 351 | if (pix[i] < 200) |
mjr | 86:e30a1f60f783 | 352 | ++nDark; |
mjr | 86:e30a1f60f783 | 353 | } |
mjr | 86:e30a1f60f783 | 354 | |
mjr | 86:e30a1f60f783 | 355 | // Figure the percentage of black pixels: the left bar is |
mjr | 86:e30a1f60f783 | 356 | // all black pixels, and 50% of each bit is black pixels. |
mjr | 86:e30a1f60f783 | 357 | int targetDark = leftBarWidth + (nBits * bitWidth)/2; |
mjr | 86:e30a1f60f783 | 358 | |
mjr | 86:e30a1f60f783 | 359 | // Increase exposure time if too many pixels are below the |
mjr | 86:e30a1f60f783 | 360 | // halfway point; decrease it if too many pixels are above. |
mjr | 86:e30a1f60f783 | 361 | int d = nDark - targetDark; |
mjr | 86:e30a1f60f783 | 362 | if (d > 5 || d < -5) |
mjr | 86:e30a1f60f783 | 363 | { |
mjr | 86:e30a1f60f783 | 364 | axcTime += d; |
mjr | 86:e30a1f60f783 | 365 | } |
mjr | 86:e30a1f60f783 | 366 | |
mjr | 86:e30a1f60f783 | 367 | |
mjr | 86:e30a1f60f783 | 368 | #elif 0 //$$$ |
mjr | 86:e30a1f60f783 | 369 | // Count exposure levels of pixels in the left and right margins |
mjr | 86:e30a1f60f783 | 370 | BarCodeExposureCounter counter; |
mjr | 86:e30a1f60f783 | 371 | for (int i = 0 ; i < leftBarMaxOfs/2 ; ++i) |
mjr | 86:e30a1f60f783 | 372 | { |
mjr | 86:e30a1f60f783 | 373 | // count the pixels at the left and right margins |
mjr | 86:e30a1f60f783 | 374 | counter.count(pix[i]); |
mjr | 86:e30a1f60f783 | 375 | counter.count(pix[npix - i - 1]); |
mjr | 86:e30a1f60f783 | 376 | } |
mjr | 86:e30a1f60f783 | 377 | |
mjr | 86:e30a1f60f783 | 378 | // The margin is all white, so try to get all of these pixels |
mjr | 86:e30a1f60f783 | 379 | // in the bright range, but not saturated. That should give us |
mjr | 86:e30a1f60f783 | 380 | // the best overall contrast throughout the image. |
mjr | 86:e30a1f60f783 | 381 | if (counter.nSat > 0) |
mjr | 86:e30a1f60f783 | 382 | { |
mjr | 86:e30a1f60f783 | 383 | // overexposed - reduce exposure time |
mjr | 86:e30a1f60f783 | 384 | if (axcTime > 5) |
mjr | 86:e30a1f60f783 | 385 | axcTime -= 5; |
mjr | 86:e30a1f60f783 | 386 | else |
mjr | 86:e30a1f60f783 | 387 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 388 | } |
mjr | 86:e30a1f60f783 | 389 | else if (counter.nBright < leftBarMaxOfs) |
mjr | 86:e30a1f60f783 | 390 | { |
mjr | 86:e30a1f60f783 | 391 | // they're not all in the bright range - increase exposure time |
mjr | 86:e30a1f60f783 | 392 | axcTime += 5; |
mjr | 86:e30a1f60f783 | 393 | } |
mjr | 86:e30a1f60f783 | 394 | |
mjr | 86:e30a1f60f783 | 395 | #else // $$$ |
mjr | 82:4f6209cb5c33 | 396 | // Count the number of pixels near total darkness and |
mjr | 82:4f6209cb5c33 | 397 | // total saturation |
mjr | 86:e30a1f60f783 | 398 | int nZero = 0, nDark = 0, nBri = 0, nSat = 0; |
mjr | 82:4f6209cb5c33 | 399 | for (int i = 0 ; i < npix ; ++i) |
mjr | 82:4f6209cb5c33 | 400 | { |
mjr | 82:4f6209cb5c33 | 401 | int pi = pix[i]; |
mjr | 86:e30a1f60f783 | 402 | if (pi <= 2) |
mjr | 86:e30a1f60f783 | 403 | ++nZero; |
mjr | 86:e30a1f60f783 | 404 | else if (pi < 12) |
mjr | 82:4f6209cb5c33 | 405 | ++nDark; |
mjr | 86:e30a1f60f783 | 406 | else if (pi >= 254) |
mjr | 82:4f6209cb5c33 | 407 | ++nSat; |
mjr | 86:e30a1f60f783 | 408 | else if (pi > 242) |
mjr | 86:e30a1f60f783 | 409 | ++nBri; |
mjr | 82:4f6209cb5c33 | 410 | } |
mjr | 82:4f6209cb5c33 | 411 | |
mjr | 82:4f6209cb5c33 | 412 | // If more than 30% of pixels are near total darkness, increase |
mjr | 82:4f6209cb5c33 | 413 | // the exposure time. If more than 30% are near total saturation, |
mjr | 82:4f6209cb5c33 | 414 | // decrease the exposure time. |
mjr | 86:e30a1f60f783 | 415 | int pct5 = uint32_t(npix * 3277) >> 16; |
mjr | 82:4f6209cb5c33 | 416 | int pct30 = uint32_t(npix * 19661) >> 16; |
mjr | 82:4f6209cb5c33 | 417 | int pct50 = uint32_t(npix) >> 1; |
mjr | 86:e30a1f60f783 | 418 | if (nSat == 0) |
mjr | 86:e30a1f60f783 | 419 | { |
mjr | 86:e30a1f60f783 | 420 | // no saturated pixels - increase exposure time |
mjr | 86:e30a1f60f783 | 421 | axcTime += 5; |
mjr | 86:e30a1f60f783 | 422 | } |
mjr | 86:e30a1f60f783 | 423 | else if (nSat > pct5) |
mjr | 86:e30a1f60f783 | 424 | { |
mjr | 86:e30a1f60f783 | 425 | if (axcTime > 5) |
mjr | 86:e30a1f60f783 | 426 | axcTime -= 5; |
mjr | 86:e30a1f60f783 | 427 | else |
mjr | 86:e30a1f60f783 | 428 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 429 | } |
mjr | 86:e30a1f60f783 | 430 | else if (nZero == 0) |
mjr | 86:e30a1f60f783 | 431 | { |
mjr | 86:e30a1f60f783 | 432 | // no totally dark pixels - decrease exposure time |
mjr | 86:e30a1f60f783 | 433 | if (axcTime > 5) |
mjr | 86:e30a1f60f783 | 434 | axcTime -= 5; |
mjr | 86:e30a1f60f783 | 435 | else |
mjr | 86:e30a1f60f783 | 436 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 437 | } |
mjr | 86:e30a1f60f783 | 438 | else if (nZero > pct5) |
mjr | 86:e30a1f60f783 | 439 | { |
mjr | 86:e30a1f60f783 | 440 | axcTime += 5; |
mjr | 86:e30a1f60f783 | 441 | } |
mjr | 86:e30a1f60f783 | 442 | else if (nZero > pct30 || (nDark > pct50 && nSat < pct30)) |
mjr | 82:4f6209cb5c33 | 443 | { |
mjr | 82:4f6209cb5c33 | 444 | // very dark - increase exposure time a lot |
mjr | 82:4f6209cb5c33 | 445 | if (axcTime < 450) |
mjr | 82:4f6209cb5c33 | 446 | axcTime += 50; |
mjr | 82:4f6209cb5c33 | 447 | } |
mjr | 82:4f6209cb5c33 | 448 | else if (nDark > pct30 && nSat < pct30) |
mjr | 82:4f6209cb5c33 | 449 | { |
mjr | 82:4f6209cb5c33 | 450 | // dark - increase exposure time a bit |
mjr | 82:4f6209cb5c33 | 451 | if (axcTime < 490) |
mjr | 82:4f6209cb5c33 | 452 | axcTime += 10; |
mjr | 82:4f6209cb5c33 | 453 | } |
mjr | 86:e30a1f60f783 | 454 | else if (nSat > pct30 || (nBri > pct50 && nDark < pct30)) |
mjr | 82:4f6209cb5c33 | 455 | { |
mjr | 82:4f6209cb5c33 | 456 | // very overexposed - decrease exposure time a lot |
mjr | 82:4f6209cb5c33 | 457 | if (axcTime > 50) |
mjr | 82:4f6209cb5c33 | 458 | axcTime -= 50; |
mjr | 82:4f6209cb5c33 | 459 | else |
mjr | 82:4f6209cb5c33 | 460 | axcTime = 0; |
mjr | 82:4f6209cb5c33 | 461 | } |
mjr | 86:e30a1f60f783 | 462 | else if (nBri > pct30 && nDark < pct30) |
mjr | 82:4f6209cb5c33 | 463 | { |
mjr | 82:4f6209cb5c33 | 464 | // overexposed - decrease exposure time a little |
mjr | 82:4f6209cb5c33 | 465 | if (axcTime > 10) |
mjr | 82:4f6209cb5c33 | 466 | axcTime -= 10; |
mjr | 82:4f6209cb5c33 | 467 | else |
mjr | 82:4f6209cb5c33 | 468 | axcTime = 0; |
mjr | 82:4f6209cb5c33 | 469 | } |
mjr | 86:e30a1f60f783 | 470 | #endif |
mjr | 86:e30a1f60f783 | 471 | |
mjr | 86:e30a1f60f783 | 472 | // don't allow the exposure time to go over 2.5ms |
mjr | 86:e30a1f60f783 | 473 | if (int(axcTime) < 0) |
mjr | 86:e30a1f60f783 | 474 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 475 | if (axcTime > 2500) |
mjr | 86:e30a1f60f783 | 476 | axcTime = 2500; |
mjr | 82:4f6209cb5c33 | 477 | } |
mjr | 82:4f6209cb5c33 | 478 | }; |
mjr | 82:4f6209cb5c33 | 479 | |
mjr | 86:e30a1f60f783 | 480 | // TSL1401CL - 128-bit image sensor, used as a bar code reader |
mjr | 86:e30a1f60f783 | 481 | class PlungerSensorTSL1401CL: public PlungerSensorBarCodeTSL14xx< |
mjr | 86:e30a1f60f783 | 482 | 10, // number of bits in code |
mjr | 86:e30a1f60f783 | 483 | 0, // left delimiter bar width in pixels (0 for none) |
mjr | 86:e30a1f60f783 | 484 | 24, // maximum left margin width in pixels |
mjr | 86:e30a1f60f783 | 485 | 12> // pixel width of each bit |
mjr | 82:4f6209cb5c33 | 486 | { |
mjr | 82:4f6209cb5c33 | 487 | public: |
mjr | 82:4f6209cb5c33 | 488 | PlungerSensorTSL1401CL(PinName si, PinName clock, PinName a0) |
mjr | 82:4f6209cb5c33 | 489 | : PlungerSensorBarCodeTSL14xx(128, si, clock, a0) |
mjr | 82:4f6209cb5c33 | 490 | { |
mjr | 82:4f6209cb5c33 | 491 | } |
mjr | 82:4f6209cb5c33 | 492 | }; |
mjr | 82:4f6209cb5c33 | 493 | |
mjr | 82:4f6209cb5c33 | 494 | #endif |