Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Plunger/barCodeSensor.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 104:6e06e0f4b476
- Correct information regarding your last merge
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 | 87:8d35c74403af | 8 | // We use a reflected Gray code, optically encoded in black/white pixel |
mjr | 87:8d35c74403af | 9 | // patterns. Each bit is represented by a fixed-width area. Half the |
mjr | 87:8d35c74403af | 10 | // pixels in every bit are white, and half are black. A '0' bit is |
mjr | 87:8d35c74403af | 11 | // represented by black pixels in the left half and white pixels in the |
mjr | 87:8d35c74403af | 12 | // right half, and a '1' bit is white on the left and black on the right. |
mjr | 87:8d35c74403af | 13 | // To read a bit, we identify the set of pixels covering the bit's fixed |
mjr | 87:8d35c74403af | 14 | // area in the code, then we see if the left or right half is brighter. |
mjr | 87:8d35c74403af | 15 | // |
mjr | 87:8d35c74403af | 16 | // (This optical encoding scheme is based on Manchester coding, which is |
mjr | 87:8d35c74403af | 17 | // normally used in the context of serial protocols, but translates to |
mjr | 87:8d35c74403af | 18 | // bar codes straightforwardly. Replace the serial protocol's time |
mjr | 87:8d35c74403af | 19 | // dimension with the spatial dimension across the bar, and replace the |
mjr | 87:8d35c74403af | 20 | // high/low wire voltage levels with white/black pixels.) |
mjr | 82:4f6209cb5c33 | 21 | // |
mjr | 86:e30a1f60f783 | 22 | // Gray codes are ideal for this type of application. Gray codes are |
mjr | 86:e30a1f60f783 | 23 | // defined such that each code point differs in exactly one bit from each |
mjr | 86:e30a1f60f783 | 24 | // adjacent code point. This provides natural error correction when used |
mjr | 86:e30a1f60f783 | 25 | // as a position scale, since any single-bit error will yield a code point |
mjr | 86:e30a1f60f783 | 26 | // reading that's only one spot off from the true position. So a bit read |
mjr | 86:e30a1f60f783 | 27 | // error acts like a reduction in precision. Likewise, any time the sensor |
mjr | 86:e30a1f60f783 | 28 | // is halfway between two code points, only one bit will be ambiguous, so |
mjr | 86:e30a1f60f783 | 29 | // the reading will come out as one of points on either side of the true |
mjr | 86:e30a1f60f783 | 30 | // position. Finally, motion blur will have the same effect, of creating |
mjr | 86:e30a1f60f783 | 31 | // ambiguity in the least significant bits, and thus giving us a reading |
mjr | 87:8d35c74403af | 32 | // that's correct to as many bits as we can make out. |
mjr | 82:4f6209cb5c33 | 33 | // |
mjr | 87:8d35c74403af | 34 | // The half-and-half optical coding also has good properties for our |
mjr | 87:8d35c74403af | 35 | // purposes. The fixed-width bit regions require essentially no CPU work |
mjr | 87:8d35c74403af | 36 | // to find the bits, which is good because we're using a fairly slow CPU. |
mjr | 87:8d35c74403af | 37 | // The half white/half black coding of each pixel makes every pixel |
mjr | 87:8d35c74403af | 38 | // self-relative in terms of brightness, so we don't need to figure the |
mjr | 87:8d35c74403af | 39 | // black and white thresholds globally for the whole image. That makes |
mjr | 87:8d35c74403af | 40 | // the physical device engineering and installation easier because the |
mjr | 87:8d35c74403af | 41 | // software can tolerate a fairly wide range of lighting conditions. |
mjr | 82:4f6209cb5c33 | 42 | // |
mjr | 82:4f6209cb5c33 | 43 | |
mjr | 82:4f6209cb5c33 | 44 | #ifndef _BARCODESENSOR_H_ |
mjr | 82:4f6209cb5c33 | 45 | #define _BARCODESENSOR_H_ |
mjr | 82:4f6209cb5c33 | 46 | |
mjr | 82:4f6209cb5c33 | 47 | #include "plunger.h" |
mjr | 87:8d35c74403af | 48 | |
mjr | 87:8d35c74403af | 49 | // Gray code to binary mapping for our special coding. This is a custom |
mjr | 87:8d35c74403af | 50 | // 7-bit code, minimum run length 6, 110 positions populated. The minimum |
mjr | 87:8d35c74403af | 51 | // run length is the minimum number of consecutive code points where each |
mjr | 87:8d35c74403af | 52 | // bit must remain fixed. For out optical coding, this defines the smallest |
mjr | 87:8d35c74403af | 53 | // "island" size for a black or white bar horizontally. Small features are |
mjr | 87:8d35c74403af | 54 | // prone to light scattering that makes them appear gray on the sensor. |
mjr | 87:8d35c74403af | 55 | // Larger features are less subject to scatter, making them easier to |
mjr | 87:8d35c74403af | 56 | // distinguish by brightness level. |
mjr | 87:8d35c74403af | 57 | static const uint8_t grayToBin[] = { |
mjr | 87:8d35c74403af | 58 | 0, 1, 83, 2, 71, 100, 84, 3, 69, 102, 82, 128, 70, 101, 57, 4, // 0-15 |
mjr | 87:8d35c74403af | 59 | 35, 50, 36, 37, 86, 87, 85, 128, 34, 103, 21, 104, 128, 128, 20, 5, // 16-31 |
mjr | 87:8d35c74403af | 60 | 11, 128, 24, 25, 98, 99, 97, 40, 68, 67, 81, 80, 55, 54, 56, 41, // 32-47 |
mjr | 87:8d35c74403af | 61 | 10, 51, 23, 38, 128, 52, 128, 39, 9, 66, 22, 128, 8, 53, 7, 6, // 48-63 |
mjr | 87:8d35c74403af | 62 | 47, 14, 60, 128, 72, 15, 59, 16, 46, 91, 93, 92, 45, 128, 58, 17, // 64-79 |
mjr | 87:8d35c74403af | 63 | 48, 49, 61, 62, 73, 88, 74, 75, 33, 90, 106, 105, 32, 89, 19, 18, // 80-95 |
mjr | 87:8d35c74403af | 64 | 12, 13, 95, 26, 128, 28, 96, 27, 128, 128, 94, 79, 44, 29, 43, 42, // 96-111 |
mjr | 87:8d35c74403af | 65 | 128, 64, 128, 63, 110, 128, 109, 76, 128, 65, 107, 78, 31, 30, 108, 77 // 112-127 |
mjr | 87:8d35c74403af | 66 | }; |
mjr | 87:8d35c74403af | 67 | |
mjr | 87:8d35c74403af | 68 | |
mjr | 87:8d35c74403af | 69 | // Auto-exposure counter |
mjr | 87:8d35c74403af | 70 | class BarCodeExposureCounter |
mjr | 87:8d35c74403af | 71 | { |
mjr | 87:8d35c74403af | 72 | public: |
mjr | 87:8d35c74403af | 73 | BarCodeExposureCounter() |
mjr | 87:8d35c74403af | 74 | { |
mjr | 87:8d35c74403af | 75 | nDark = 0; |
mjr | 87:8d35c74403af | 76 | nBright = 0; |
mjr | 87:8d35c74403af | 77 | nZero = 0; |
mjr | 87:8d35c74403af | 78 | nSat = 0; |
mjr | 87:8d35c74403af | 79 | } |
mjr | 87:8d35c74403af | 80 | |
mjr | 87:8d35c74403af | 81 | inline void count(int pix) |
mjr | 87:8d35c74403af | 82 | { |
mjr | 87:8d35c74403af | 83 | if (pix <= 2) |
mjr | 87:8d35c74403af | 84 | ++nZero; |
mjr | 87:8d35c74403af | 85 | else if (pix < 12) |
mjr | 87:8d35c74403af | 86 | ++nDark; |
mjr | 87:8d35c74403af | 87 | else if (pix >= 253) |
mjr | 87:8d35c74403af | 88 | ++nSat; |
mjr | 87:8d35c74403af | 89 | else if (pix > 200) |
mjr | 87:8d35c74403af | 90 | ++nBright; |
mjr | 87:8d35c74403af | 91 | } |
mjr | 87:8d35c74403af | 92 | |
mjr | 87:8d35c74403af | 93 | int nDark; // dark pixels |
mjr | 87:8d35c74403af | 94 | int nBright; // bright pixels |
mjr | 87:8d35c74403af | 95 | int nZero; // pixels at zero brightness |
mjr | 87:8d35c74403af | 96 | int nSat; // pixels at full saturation |
mjr | 87:8d35c74403af | 97 | }; |
mjr | 87:8d35c74403af | 98 | |
mjr | 82:4f6209cb5c33 | 99 | |
mjr | 82:4f6209cb5c33 | 100 | // Base class for bar-code sensors |
mjr | 86:e30a1f60f783 | 101 | // |
mjr | 86:e30a1f60f783 | 102 | // This is a template class with template parameters for the bar |
mjr | 86:e30a1f60f783 | 103 | // code pixel structure. The bar code layout is fixed for a given |
mjr | 86:e30a1f60f783 | 104 | // sensor type. We can assume fixed pixel sizes because we don't |
mjr | 86:e30a1f60f783 | 105 | // have to process arbitrary images. We only have to read scales |
mjr | 86:e30a1f60f783 | 106 | // specially prepared for this application, so we can count on them |
mjr | 86:e30a1f60f783 | 107 | // being printed at an exact size relative to the sensor pixels. |
mjr | 86:e30a1f60f783 | 108 | // |
mjr | 86:e30a1f60f783 | 109 | // nBits = Number of bits in the code |
mjr | 86:e30a1f60f783 | 110 | // |
mjr | 86:e30a1f60f783 | 111 | // leftBarWidth = Width in pixels of delimiting left bar. The code is |
mjr | 86:e30a1f60f783 | 112 | // delimited by a black bar on the "left" end, nearest pixel 0. This |
mjr | 86:e30a1f60f783 | 113 | // gives the pixel width of the bar. |
mjr | 86:e30a1f60f783 | 114 | // |
mjr | 86:e30a1f60f783 | 115 | // leftBarMaxOfs = Maximum offset of the delimiting bar from the left |
mjr | 86:e30a1f60f783 | 116 | // edge of the sensor (pixel 0), in pixels |
mjr | 86:e30a1f60f783 | 117 | // |
mjr | 86:e30a1f60f783 | 118 | // bitWidth = Width of each bit in pixels. This is the width of the |
mjr | 86:e30a1f60f783 | 119 | // full bit, including both "half bits" - it's the full white/black or |
mjr | 86:e30a1f60f783 | 120 | // black/white pattern area. |
mjr | 86:e30a1f60f783 | 121 | |
mjr | 87:8d35c74403af | 122 | struct BarCodeProcessResult |
mjr | 87:8d35c74403af | 123 | { |
mjr | 87:8d35c74403af | 124 | int pixofs; |
mjr | 87:8d35c74403af | 125 | int raw; |
mjr | 87:8d35c74403af | 126 | int mask; |
mjr | 87:8d35c74403af | 127 | }; |
mjr | 87:8d35c74403af | 128 | |
mjr | 86:e30a1f60f783 | 129 | template <int nBits, int leftBarWidth, int leftBarMaxOfs, int bitWidth> |
mjr | 87:8d35c74403af | 130 | class PlungerSensorBarCode: public PlungerSensorImage<BarCodeProcessResult> |
mjr | 82:4f6209cb5c33 | 131 | { |
mjr | 82:4f6209cb5c33 | 132 | public: |
mjr | 104:6e06e0f4b476 | 133 | PlungerSensorBarCode(PlungerSensorImageInterface &sensor, int npix) |
mjr | 87:8d35c74403af | 134 | : PlungerSensorImage(sensor, npix, (1 << nBits) - 1) |
mjr | 87:8d35c74403af | 135 | { |
mjr | 87:8d35c74403af | 136 | startOfs = 0; |
mjr | 87:8d35c74403af | 137 | } |
mjr | 87:8d35c74403af | 138 | |
mjr | 87:8d35c74403af | 139 | // process a configuration change |
mjr | 87:8d35c74403af | 140 | virtual void onConfigChange(int varno, Config &cfg) |
mjr | 87:8d35c74403af | 141 | { |
mjr | 87:8d35c74403af | 142 | // check for bar-code variables |
mjr | 87:8d35c74403af | 143 | switch (varno) |
mjr | 87:8d35c74403af | 144 | { |
mjr | 87:8d35c74403af | 145 | case 20: |
mjr | 87:8d35c74403af | 146 | // bar code offset |
mjr | 87:8d35c74403af | 147 | startOfs = cfg.plunger.barCode.startPix; |
mjr | 87:8d35c74403af | 148 | break; |
mjr | 87:8d35c74403af | 149 | } |
mjr | 87:8d35c74403af | 150 | |
mjr | 87:8d35c74403af | 151 | // do the generic work |
mjr | 87:8d35c74403af | 152 | PlungerSensorImage::onConfigChange(varno, cfg); |
mjr | 87:8d35c74403af | 153 | } |
mjr | 87:8d35c74403af | 154 | |
mjr | 87:8d35c74403af | 155 | protected: |
mjr | 86:e30a1f60f783 | 156 | // process the image |
mjr | 87:8d35c74403af | 157 | virtual bool process(const uint8_t *pix, int npix, int &pos, BarCodeProcessResult &res) |
mjr | 82:4f6209cb5c33 | 158 | { |
mjr | 87:8d35c74403af | 159 | // adjust auto-exposure |
mjr | 87:8d35c74403af | 160 | adjustExposure(pix, npix); |
mjr | 87:8d35c74403af | 161 | |
mjr | 87:8d35c74403af | 162 | // clear the result descriptor |
mjr | 87:8d35c74403af | 163 | res.pixofs = 0; |
mjr | 87:8d35c74403af | 164 | res.raw = 0; |
mjr | 87:8d35c74403af | 165 | res.mask = 0; |
mjr | 87:8d35c74403af | 166 | |
mjr | 86:e30a1f60f783 | 167 | #if 0 // $$$ |
mjr | 86:e30a1f60f783 | 168 | |
mjr | 86:e30a1f60f783 | 169 | // scan from the left edge until we find the fixed '0' start bit |
mjr | 86:e30a1f60f783 | 170 | for (int i = 0 ; i < leftBarMaxOfs ; ++i, ++pix) |
mjr | 86:e30a1f60f783 | 171 | { |
mjr | 86:e30a1f60f783 | 172 | // check for the '0' bit |
mjr | 86:e30a1f60f783 | 173 | if (readBit8(pix) == 0) |
mjr | 86:e30a1f60f783 | 174 | { |
mjr | 86:e30a1f60f783 | 175 | // got it - skip the start bit |
mjr | 86:e30a1f60f783 | 176 | pix += bitWidth; |
mjr | 86:e30a1f60f783 | 177 | |
mjr | 86:e30a1f60f783 | 178 | // read the gray code bits |
mjr | 86:e30a1f60f783 | 179 | int gray = 0; |
mjr | 86:e30a1f60f783 | 180 | for (int j = 0 ; j < nBits ; ++j, pix += bitWidth) |
mjr | 86:e30a1f60f783 | 181 | { |
mjr | 86:e30a1f60f783 | 182 | // read the bit; return failure if we can't decode a bit |
mjr | 86:e30a1f60f783 | 183 | int bit = readBit8(pix); |
mjr | 86:e30a1f60f783 | 184 | if (bit < 0) |
mjr | 86:e30a1f60f783 | 185 | return false; |
mjr | 86:e30a1f60f783 | 186 | |
mjr | 86:e30a1f60f783 | 187 | // shift it into the code |
mjr | 86:e30a1f60f783 | 188 | gray = (gray << 1) | bit; |
mjr | 86:e30a1f60f783 | 189 | } |
mjr | 86:e30a1f60f783 | 190 | } |
mjr | 86:e30a1f60f783 | 191 | |
mjr | 86:e30a1f60f783 | 192 | // convert the gray code to binary |
mjr | 86:e30a1f60f783 | 193 | int bin = grayToBin(gray); |
mjr | 86:e30a1f60f783 | 194 | |
mjr | 86:e30a1f60f783 | 195 | // compute the parity of the binary value |
mjr | 86:e30a1f60f783 | 196 | int parity = 0; |
mjr | 86:e30a1f60f783 | 197 | for (int j = 0 ; j < nBits ; ++j) |
mjr | 86:e30a1f60f783 | 198 | parity ^= bin >> j; |
mjr | 86:e30a1f60f783 | 199 | |
mjr | 86:e30a1f60f783 | 200 | // figure the bit required for odd parity |
mjr | 86:e30a1f60f783 | 201 | int odd = (parity & 0x01) ^ 0x01; |
mjr | 86:e30a1f60f783 | 202 | |
mjr | 86:e30a1f60f783 | 203 | // read the check bit |
mjr | 86:e30a1f60f783 | 204 | int bit = readBit8(pix); |
mjr | 86:e30a1f60f783 | 205 | if (pix < 0) |
mjr | 86:e30a1f60f783 | 206 | return false; |
mjr | 86:e30a1f60f783 | 207 | |
mjr | 86:e30a1f60f783 | 208 | // check that it matches the expected parity |
mjr | 86:e30a1f60f783 | 209 | if (bit != odd) |
mjr | 86:e30a1f60f783 | 210 | return false; |
mjr | 86:e30a1f60f783 | 211 | |
mjr | 86:e30a1f60f783 | 212 | // success |
mjr | 86:e30a1f60f783 | 213 | pos = bin; |
mjr | 86:e30a1f60f783 | 214 | return true; |
mjr | 86:e30a1f60f783 | 215 | } |
mjr | 86:e30a1f60f783 | 216 | |
mjr | 86:e30a1f60f783 | 217 | // no code found |
mjr | 82:4f6209cb5c33 | 218 | return false; |
mjr | 86:e30a1f60f783 | 219 | |
mjr | 86:e30a1f60f783 | 220 | #else |
mjr | 86:e30a1f60f783 | 221 | int barStart = leftBarMaxOfs/2; |
mjr | 86:e30a1f60f783 | 222 | if (leftBarWidth != 0) // $$$ |
mjr | 86:e30a1f60f783 | 223 | { |
mjr | 86:e30a1f60f783 | 224 | // Find the black bar on the left side (nearest pixel 0) that |
mjr | 86:e30a1f60f783 | 225 | // delimits the start of the bar code. To find it, first figure |
mjr | 86:e30a1f60f783 | 226 | // the average brightness over the left margin up to the maximum |
mjr | 86:e30a1f60f783 | 227 | // allowable offset, then look for the bar by finding the first |
mjr | 86:e30a1f60f783 | 228 | // bar-width run of pixels that are darker than the average. |
mjr | 86:e30a1f60f783 | 229 | int lsum = 0; |
mjr | 86:e30a1f60f783 | 230 | for (int x = 1 ; x <= leftBarMaxOfs ; ++x) |
mjr | 86:e30a1f60f783 | 231 | lsum += pix[x]; |
mjr | 86:e30a1f60f783 | 232 | int lavg = lsum / leftBarMaxOfs; |
mjr | 86:e30a1f60f783 | 233 | |
mjr | 86:e30a1f60f783 | 234 | // now find the first dark edge |
mjr | 86:e30a1f60f783 | 235 | for (int x = 0 ; x < leftBarMaxOfs ; ++x) |
mjr | 86:e30a1f60f783 | 236 | { |
mjr | 86:e30a1f60f783 | 237 | // if this pixel is dark, and one of the next two is dark, |
mjr | 86:e30a1f60f783 | 238 | // take it as the edge |
mjr | 86:e30a1f60f783 | 239 | if (pix[x] < lavg && (pix[x+1] < lavg || pix[x+2] < lavg)) |
mjr | 86:e30a1f60f783 | 240 | { |
mjr | 86:e30a1f60f783 | 241 | // move past the delimier |
mjr | 86:e30a1f60f783 | 242 | barStart = x + leftBarWidth; |
mjr | 86:e30a1f60f783 | 243 | break; |
mjr | 86:e30a1f60f783 | 244 | } |
mjr | 86:e30a1f60f783 | 245 | } |
mjr | 86:e30a1f60f783 | 246 | } |
mjr | 86:e30a1f60f783 | 247 | else |
mjr | 86:e30a1f60f783 | 248 | { |
mjr | 87:8d35c74403af | 249 | // start at the fixed pixel offset |
mjr | 87:8d35c74403af | 250 | barStart = startOfs; |
mjr | 86:e30a1f60f783 | 251 | } |
mjr | 86:e30a1f60f783 | 252 | |
mjr | 87:8d35c74403af | 253 | // Start with zero in the barcode and success mask. The mask |
mjr | 87:8d35c74403af | 254 | // indicates which bits we were able to read successfully: a |
mjr | 87:8d35c74403af | 255 | // '1' bit in the mask indicates that the corresponding bit |
mjr | 87:8d35c74403af | 256 | // position in 'barcode' was successfully read, a '0' bit means |
mjr | 87:8d35c74403af | 257 | // that the image was too fuzzy to read. |
mjr | 87:8d35c74403af | 258 | int barcode = 0, mask = 0; |
mjr | 87:8d35c74403af | 259 | |
mjr | 86:e30a1f60f783 | 260 | // Scan the bits |
mjr | 86:e30a1f60f783 | 261 | for (int bit = 0, x0 = barStart; bit < nBits ; ++bit, x0 += bitWidth) |
mjr | 86:e30a1f60f783 | 262 | { |
mjr | 87:8d35c74403af | 263 | #if 0 |
mjr | 87:8d35c74403af | 264 | // Figure the extent of this bit. The last bit is double |
mjr | 87:8d35c74403af | 265 | // the width of the other bits, to give us a better chance |
mjr | 87:8d35c74403af | 266 | // of making out the small features of the last bit. |
mjr | 87:8d35c74403af | 267 | int w = bitWidth; |
mjr | 87:8d35c74403af | 268 | if (bit == nBits - 1) w *= 2; |
mjr | 87:8d35c74403af | 269 | #else |
mjr | 87:8d35c74403af | 270 | // width of the bit |
mjr | 87:8d35c74403af | 271 | const int w = bitWidth; |
mjr | 87:8d35c74403af | 272 | #endif |
mjr | 87:8d35c74403af | 273 | |
mjr | 87:8d35c74403af | 274 | // figure the bit's internal pixel layout |
mjr | 87:8d35c74403af | 275 | int halfBitWidth = w / 2; |
mjr | 87:8d35c74403af | 276 | int x1 = x0 + halfBitWidth; // midpoint |
mjr | 87:8d35c74403af | 277 | int x2 = x0 + w; // right edge |
mjr | 87:8d35c74403af | 278 | |
mjr | 87:8d35c74403af | 279 | // make sure we didn't go out of bounds |
mjr | 86:e30a1f60f783 | 280 | if (x1 > npix) x1 = npix; |
mjr | 86:e30a1f60f783 | 281 | if (x2 > npix) x2 = npix; |
mjr | 86:e30a1f60f783 | 282 | |
mjr | 87:8d35c74403af | 283 | #if 0 |
mjr | 86:e30a1f60f783 | 284 | // get the average of the pixels over the bit |
mjr | 86:e30a1f60f783 | 285 | int sum = 0; |
mjr | 86:e30a1f60f783 | 286 | for (int x = x0 ; x < x2 ; ++x) |
mjr | 86:e30a1f60f783 | 287 | sum += pix[x]; |
mjr | 87:8d35c74403af | 288 | int avg = sum / w; |
mjr | 86:e30a1f60f783 | 289 | // Scan the left and right sections. Classify each |
mjr | 86:e30a1f60f783 | 290 | // section according to whether the majority of its |
mjr | 86:e30a1f60f783 | 291 | // pixels are above or below the local average. |
mjr | 86:e30a1f60f783 | 292 | int lsum = 0, rsum = 0; |
mjr | 86:e30a1f60f783 | 293 | for (int x = x0 + 1 ; x < x1 - 1 ; ++x) |
mjr | 86:e30a1f60f783 | 294 | lsum += (pix[x] < avg ? 0 : 1); |
mjr | 86:e30a1f60f783 | 295 | for (int x = x1 + 1 ; x < x2 - 1 ; ++x) |
mjr | 86:e30a1f60f783 | 296 | rsum += (pix[x] < avg ? 0 : 1); |
mjr | 87:8d35c74403af | 297 | #else |
mjr | 87:8d35c74403af | 298 | // Sum the pixel readings in each half-bit. Ignore |
mjr | 87:8d35c74403af | 299 | // the first and last bit of each section, since these |
mjr | 87:8d35c74403af | 300 | // could be contaminated with scattered light from the |
mjr | 87:8d35c74403af | 301 | // adjacent half-bit. On the right half, hew to the |
mjr | 87:8d35c74403af | 302 | // right side if the overall pixel width is odd. |
mjr | 87:8d35c74403af | 303 | int lsum = 0, rsum = 0; |
mjr | 87:8d35c74403af | 304 | for (int x = x0 + 1 ; x < x1 - 1 ; ++x) |
mjr | 87:8d35c74403af | 305 | lsum += pix[x]; |
mjr | 87:8d35c74403af | 306 | for (int x = x2 - halfBitWidth + 1 ; x < x2 - 1 ; ++x) |
mjr | 87:8d35c74403af | 307 | rsum += pix[x]; |
mjr | 87:8d35c74403af | 308 | #endif |
mjr | 86:e30a1f60f783 | 309 | |
mjr | 87:8d35c74403af | 310 | // shift a zero bit into the code and success mask |
mjr | 87:8d35c74403af | 311 | barcode <<= 1; |
mjr | 87:8d35c74403af | 312 | mask <<= 1; |
mjr | 86:e30a1f60f783 | 313 | |
mjr | 87:8d35c74403af | 314 | // Brightness difference required per pixel. Higher values |
mjr | 87:8d35c74403af | 315 | // require greater contrast to make a reading, which reduces |
mjr | 87:8d35c74403af | 316 | // spurious readings at the cost of reducing the overall |
mjr | 87:8d35c74403af | 317 | // success rate. The right level depends on the quality of |
mjr | 87:8d35c74403af | 318 | // the optical system. Setting this to zero makes us maximally |
mjr | 87:8d35c74403af | 319 | // tolerant of low-contrast images, allowing for the simplest |
mjr | 87:8d35c74403af | 320 | // optical system. Our simple optical system suffers from |
mjr | 87:8d35c74403af | 321 | // poor focus, which in turn causes poor contrast in small |
mjr | 87:8d35c74403af | 322 | // features. |
mjr | 87:8d35c74403af | 323 | const int minDelta = 2; |
mjr | 87:8d35c74403af | 324 | |
mjr | 87:8d35c74403af | 325 | // see if we could tell the difference in brightness |
mjr | 87:8d35c74403af | 326 | int delta = lsum - rsum; |
mjr | 87:8d35c74403af | 327 | if (delta < 0) delta = -delta; |
mjr | 87:8d35c74403af | 328 | if (delta > minDelta * w/2) |
mjr | 87:8d35c74403af | 329 | { |
mjr | 87:8d35c74403af | 330 | // got it - black/white = 0, white/black = 1 |
mjr | 87:8d35c74403af | 331 | if (lsum > rsum) barcode |= 1; |
mjr | 87:8d35c74403af | 332 | mask |= 1; |
mjr | 87:8d35c74403af | 333 | } |
mjr | 86:e30a1f60f783 | 334 | } |
mjr | 86:e30a1f60f783 | 335 | |
mjr | 86:e30a1f60f783 | 336 | // decode the Gray code value to binary |
mjr | 87:8d35c74403af | 337 | pos = grayToBin[barcode]; |
mjr | 86:e30a1f60f783 | 338 | |
mjr | 87:8d35c74403af | 339 | // set the results descriptor structure |
mjr | 87:8d35c74403af | 340 | res.pixofs = barStart; |
mjr | 87:8d35c74403af | 341 | res.raw = barcode; |
mjr | 87:8d35c74403af | 342 | res.mask = mask; |
mjr | 87:8d35c74403af | 343 | |
mjr | 87:8d35c74403af | 344 | // return success if we decoded all bits, and the Gray-to-binary |
mjr | 87:8d35c74403af | 345 | // mapping was populated |
mjr | 87:8d35c74403af | 346 | return pos != (1 << nBits) && mask == ((1 << nBits) - 1); |
mjr | 86:e30a1f60f783 | 347 | #endif |
mjr | 86:e30a1f60f783 | 348 | } |
mjr | 86:e30a1f60f783 | 349 | |
mjr | 86:e30a1f60f783 | 350 | // read a bar starting at the given pixel |
mjr | 86:e30a1f60f783 | 351 | int readBit8(const uint8_t *pix) |
mjr | 86:e30a1f60f783 | 352 | { |
mjr | 86:e30a1f60f783 | 353 | // pull out the pixels for the bar |
mjr | 86:e30a1f60f783 | 354 | uint8_t s[8]; |
mjr | 86:e30a1f60f783 | 355 | memcpy(s, pix, 8); |
mjr | 86:e30a1f60f783 | 356 | |
mjr | 86:e30a1f60f783 | 357 | // sort them in brightness order (using an 8-element network sort) |
mjr | 86:e30a1f60f783 | 358 | #define SWAP(a, b) if (s[a] > s[b]) { uint8_t tmp = s[a]; s[a] = s[b]; s[b] = tmp; } |
mjr | 86:e30a1f60f783 | 359 | SWAP(0, 1); |
mjr | 86:e30a1f60f783 | 360 | SWAP(2, 3); |
mjr | 86:e30a1f60f783 | 361 | SWAP(0, 2); |
mjr | 86:e30a1f60f783 | 362 | SWAP(1, 3); |
mjr | 86:e30a1f60f783 | 363 | SWAP(1, 2); |
mjr | 86:e30a1f60f783 | 364 | SWAP(4, 5); |
mjr | 86:e30a1f60f783 | 365 | SWAP(6, 7); |
mjr | 86:e30a1f60f783 | 366 | SWAP(4, 6); |
mjr | 86:e30a1f60f783 | 367 | SWAP(5, 7); |
mjr | 86:e30a1f60f783 | 368 | SWAP(5, 6); |
mjr | 86:e30a1f60f783 | 369 | SWAP(0, 4); |
mjr | 86:e30a1f60f783 | 370 | SWAP(1, 5); |
mjr | 86:e30a1f60f783 | 371 | SWAP(1, 4); |
mjr | 86:e30a1f60f783 | 372 | SWAP(2, 6); |
mjr | 86:e30a1f60f783 | 373 | SWAP(3, 7); |
mjr | 86:e30a1f60f783 | 374 | SWAP(3, 6); |
mjr | 86:e30a1f60f783 | 375 | SWAP(2, 4); |
mjr | 86:e30a1f60f783 | 376 | SWAP(3, 5); |
mjr | 86:e30a1f60f783 | 377 | SWAP(3, 4); |
mjr | 86:e30a1f60f783 | 378 | #undef SWAP |
mjr | 86:e30a1f60f783 | 379 | |
mjr | 86:e30a1f60f783 | 380 | // figure the median brightness |
mjr | 86:e30a1f60f783 | 381 | int median = (int(s[3]) + s[4] + 1) / 2; |
mjr | 86:e30a1f60f783 | 382 | |
mjr | 86:e30a1f60f783 | 383 | // count pixels below the median on each side |
mjr | 86:e30a1f60f783 | 384 | int ldark = 0, rdark = 0; |
mjr | 86:e30a1f60f783 | 385 | for (int i = 0 ; i < 3 ; ++i) |
mjr | 86:e30a1f60f783 | 386 | { |
mjr | 86:e30a1f60f783 | 387 | if (pix[i] < median) |
mjr | 86:e30a1f60f783 | 388 | ldark++; |
mjr | 86:e30a1f60f783 | 389 | } |
mjr | 86:e30a1f60f783 | 390 | for (int i = 4 ; i < 8 ; ++i) |
mjr | 86:e30a1f60f783 | 391 | { |
mjr | 86:e30a1f60f783 | 392 | if (pix[i] < median) |
mjr | 86:e30a1f60f783 | 393 | rdark++; |
mjr | 86:e30a1f60f783 | 394 | } |
mjr | 86:e30a1f60f783 | 395 | |
mjr | 86:e30a1f60f783 | 396 | // we need >=3 dark + >=3 light or vice versa |
mjr | 86:e30a1f60f783 | 397 | if (ldark >= 3 && rdark <= 1) |
mjr | 86:e30a1f60f783 | 398 | { |
mjr | 86:e30a1f60f783 | 399 | // dark + light = '0' bit |
mjr | 86:e30a1f60f783 | 400 | return 0; |
mjr | 86:e30a1f60f783 | 401 | } |
mjr | 86:e30a1f60f783 | 402 | if (ldark <= 1 && rdark >= 3) |
mjr | 86:e30a1f60f783 | 403 | { |
mjr | 86:e30a1f60f783 | 404 | // light + dark = '1' bit |
mjr | 86:e30a1f60f783 | 405 | return 1; |
mjr | 86:e30a1f60f783 | 406 | } |
mjr | 86:e30a1f60f783 | 407 | else |
mjr | 86:e30a1f60f783 | 408 | { |
mjr | 86:e30a1f60f783 | 409 | // ambiguous bit |
mjr | 86:e30a1f60f783 | 410 | return -1; |
mjr | 86:e30a1f60f783 | 411 | } |
mjr | 86:e30a1f60f783 | 412 | } |
mjr | 86:e30a1f60f783 | 413 | |
mjr | 86:e30a1f60f783 | 414 | // bar code sensor orientation is fixed |
mjr | 86:e30a1f60f783 | 415 | virtual int getOrientation() const { return 1; } |
mjr | 86:e30a1f60f783 | 416 | |
mjr | 87:8d35c74403af | 417 | // send extra status report headers |
mjr | 87:8d35c74403af | 418 | virtual void extraStatusHeaders(USBJoystick &js, BarCodeProcessResult &res) |
mjr | 87:8d35c74403af | 419 | { |
mjr | 87:8d35c74403af | 420 | // Send the bar code status report. We use coding type 1 (Gray code, |
mjr | 87:8d35c74403af | 421 | // Manchester pixel coding). |
mjr | 87:8d35c74403af | 422 | js.sendPlungerStatusBarcode(nBits, 1, res.pixofs, bitWidth, res.raw, res.mask); |
mjr | 87:8d35c74403af | 423 | } |
mjr | 87:8d35c74403af | 424 | |
mjr | 82:4f6209cb5c33 | 425 | // adjust the exposure |
mjr | 82:4f6209cb5c33 | 426 | void adjustExposure(const uint8_t *pix, int npix) |
mjr | 82:4f6209cb5c33 | 427 | { |
mjr | 86:e30a1f60f783 | 428 | #if 1 |
mjr | 86:e30a1f60f783 | 429 | // The Manchester code has a nice property for auto exposure |
mjr | 86:e30a1f60f783 | 430 | // control: each bit area has equal numbers of white and black |
mjr | 86:e30a1f60f783 | 431 | // pixels. So we know exactly how the overall population of |
mjr | 86:e30a1f60f783 | 432 | // pixels has to look: the bit area will be 50% black and 50% |
mjr | 86:e30a1f60f783 | 433 | // white, and the margins will be all white. For maximum |
mjr | 86:e30a1f60f783 | 434 | // contrast, target an exposure level where the black pixels |
mjr | 87:8d35c74403af | 435 | // are all below a certain brightness level and the white |
mjr | 86:e30a1f60f783 | 436 | // pixels are all above. Start by figuring the number of |
mjr | 86:e30a1f60f783 | 437 | // pixels above and below. |
mjr | 87:8d35c74403af | 438 | const int medianTarget = 160; |
mjr | 87:8d35c74403af | 439 | int nBelow = 0; |
mjr | 86:e30a1f60f783 | 440 | for (int i = 0 ; i < npix ; ++i) |
mjr | 86:e30a1f60f783 | 441 | { |
mjr | 87:8d35c74403af | 442 | if (pix[i] < medianTarget) |
mjr | 87:8d35c74403af | 443 | ++nBelow; |
mjr | 86:e30a1f60f783 | 444 | } |
mjr | 86:e30a1f60f783 | 445 | |
mjr | 87:8d35c74403af | 446 | // Figure the desired number of black pixels: the left bar is |
mjr | 86:e30a1f60f783 | 447 | // all black pixels, and 50% of each bit is black pixels. |
mjr | 87:8d35c74403af | 448 | int targetBelow = leftBarWidth + (nBits * bitWidth)/2; |
mjr | 86:e30a1f60f783 | 449 | |
mjr | 86:e30a1f60f783 | 450 | // Increase exposure time if too many pixels are below the |
mjr | 86:e30a1f60f783 | 451 | // halfway point; decrease it if too many pixels are above. |
mjr | 87:8d35c74403af | 452 | int d = nBelow - targetBelow; |
mjr | 86:e30a1f60f783 | 453 | if (d > 5 || d < -5) |
mjr | 86:e30a1f60f783 | 454 | { |
mjr | 86:e30a1f60f783 | 455 | axcTime += d; |
mjr | 86:e30a1f60f783 | 456 | } |
mjr | 86:e30a1f60f783 | 457 | |
mjr | 86:e30a1f60f783 | 458 | |
mjr | 86:e30a1f60f783 | 459 | #elif 0 //$$$ |
mjr | 86:e30a1f60f783 | 460 | // Count exposure levels of pixels in the left and right margins |
mjr | 86:e30a1f60f783 | 461 | BarCodeExposureCounter counter; |
mjr | 86:e30a1f60f783 | 462 | for (int i = 0 ; i < leftBarMaxOfs/2 ; ++i) |
mjr | 86:e30a1f60f783 | 463 | { |
mjr | 86:e30a1f60f783 | 464 | // count the pixels at the left and right margins |
mjr | 86:e30a1f60f783 | 465 | counter.count(pix[i]); |
mjr | 86:e30a1f60f783 | 466 | counter.count(pix[npix - i - 1]); |
mjr | 86:e30a1f60f783 | 467 | } |
mjr | 86:e30a1f60f783 | 468 | |
mjr | 86:e30a1f60f783 | 469 | // The margin is all white, so try to get all of these pixels |
mjr | 86:e30a1f60f783 | 470 | // in the bright range, but not saturated. That should give us |
mjr | 86:e30a1f60f783 | 471 | // the best overall contrast throughout the image. |
mjr | 86:e30a1f60f783 | 472 | if (counter.nSat > 0) |
mjr | 86:e30a1f60f783 | 473 | { |
mjr | 86:e30a1f60f783 | 474 | // overexposed - reduce exposure time |
mjr | 86:e30a1f60f783 | 475 | if (axcTime > 5) |
mjr | 86:e30a1f60f783 | 476 | axcTime -= 5; |
mjr | 86:e30a1f60f783 | 477 | else |
mjr | 86:e30a1f60f783 | 478 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 479 | } |
mjr | 86:e30a1f60f783 | 480 | else if (counter.nBright < leftBarMaxOfs) |
mjr | 86:e30a1f60f783 | 481 | { |
mjr | 86:e30a1f60f783 | 482 | // they're not all in the bright range - increase exposure time |
mjr | 86:e30a1f60f783 | 483 | axcTime += 5; |
mjr | 86:e30a1f60f783 | 484 | } |
mjr | 86:e30a1f60f783 | 485 | |
mjr | 86:e30a1f60f783 | 486 | #else // $$$ |
mjr | 82:4f6209cb5c33 | 487 | // Count the number of pixels near total darkness and |
mjr | 82:4f6209cb5c33 | 488 | // total saturation |
mjr | 86:e30a1f60f783 | 489 | int nZero = 0, nDark = 0, nBri = 0, nSat = 0; |
mjr | 82:4f6209cb5c33 | 490 | for (int i = 0 ; i < npix ; ++i) |
mjr | 82:4f6209cb5c33 | 491 | { |
mjr | 82:4f6209cb5c33 | 492 | int pi = pix[i]; |
mjr | 86:e30a1f60f783 | 493 | if (pi <= 2) |
mjr | 86:e30a1f60f783 | 494 | ++nZero; |
mjr | 86:e30a1f60f783 | 495 | else if (pi < 12) |
mjr | 82:4f6209cb5c33 | 496 | ++nDark; |
mjr | 86:e30a1f60f783 | 497 | else if (pi >= 254) |
mjr | 82:4f6209cb5c33 | 498 | ++nSat; |
mjr | 86:e30a1f60f783 | 499 | else if (pi > 242) |
mjr | 86:e30a1f60f783 | 500 | ++nBri; |
mjr | 82:4f6209cb5c33 | 501 | } |
mjr | 82:4f6209cb5c33 | 502 | |
mjr | 82:4f6209cb5c33 | 503 | // If more than 30% of pixels are near total darkness, increase |
mjr | 82:4f6209cb5c33 | 504 | // the exposure time. If more than 30% are near total saturation, |
mjr | 82:4f6209cb5c33 | 505 | // decrease the exposure time. |
mjr | 86:e30a1f60f783 | 506 | int pct5 = uint32_t(npix * 3277) >> 16; |
mjr | 82:4f6209cb5c33 | 507 | int pct30 = uint32_t(npix * 19661) >> 16; |
mjr | 82:4f6209cb5c33 | 508 | int pct50 = uint32_t(npix) >> 1; |
mjr | 86:e30a1f60f783 | 509 | if (nSat == 0) |
mjr | 86:e30a1f60f783 | 510 | { |
mjr | 86:e30a1f60f783 | 511 | // no saturated pixels - increase exposure time |
mjr | 86:e30a1f60f783 | 512 | axcTime += 5; |
mjr | 86:e30a1f60f783 | 513 | } |
mjr | 86:e30a1f60f783 | 514 | else if (nSat > pct5) |
mjr | 86:e30a1f60f783 | 515 | { |
mjr | 86:e30a1f60f783 | 516 | if (axcTime > 5) |
mjr | 86:e30a1f60f783 | 517 | axcTime -= 5; |
mjr | 86:e30a1f60f783 | 518 | else |
mjr | 86:e30a1f60f783 | 519 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 520 | } |
mjr | 86:e30a1f60f783 | 521 | else if (nZero == 0) |
mjr | 86:e30a1f60f783 | 522 | { |
mjr | 86:e30a1f60f783 | 523 | // no totally dark pixels - decrease exposure time |
mjr | 86:e30a1f60f783 | 524 | if (axcTime > 5) |
mjr | 86:e30a1f60f783 | 525 | axcTime -= 5; |
mjr | 86:e30a1f60f783 | 526 | else |
mjr | 86:e30a1f60f783 | 527 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 528 | } |
mjr | 86:e30a1f60f783 | 529 | else if (nZero > pct5) |
mjr | 86:e30a1f60f783 | 530 | { |
mjr | 86:e30a1f60f783 | 531 | axcTime += 5; |
mjr | 86:e30a1f60f783 | 532 | } |
mjr | 86:e30a1f60f783 | 533 | else if (nZero > pct30 || (nDark > pct50 && nSat < pct30)) |
mjr | 82:4f6209cb5c33 | 534 | { |
mjr | 82:4f6209cb5c33 | 535 | // very dark - increase exposure time a lot |
mjr | 82:4f6209cb5c33 | 536 | if (axcTime < 450) |
mjr | 82:4f6209cb5c33 | 537 | axcTime += 50; |
mjr | 82:4f6209cb5c33 | 538 | } |
mjr | 82:4f6209cb5c33 | 539 | else if (nDark > pct30 && nSat < pct30) |
mjr | 82:4f6209cb5c33 | 540 | { |
mjr | 82:4f6209cb5c33 | 541 | // dark - increase exposure time a bit |
mjr | 82:4f6209cb5c33 | 542 | if (axcTime < 490) |
mjr | 82:4f6209cb5c33 | 543 | axcTime += 10; |
mjr | 82:4f6209cb5c33 | 544 | } |
mjr | 86:e30a1f60f783 | 545 | else if (nSat > pct30 || (nBri > pct50 && nDark < pct30)) |
mjr | 82:4f6209cb5c33 | 546 | { |
mjr | 82:4f6209cb5c33 | 547 | // very overexposed - decrease exposure time a lot |
mjr | 82:4f6209cb5c33 | 548 | if (axcTime > 50) |
mjr | 82:4f6209cb5c33 | 549 | axcTime -= 50; |
mjr | 82:4f6209cb5c33 | 550 | else |
mjr | 82:4f6209cb5c33 | 551 | axcTime = 0; |
mjr | 82:4f6209cb5c33 | 552 | } |
mjr | 86:e30a1f60f783 | 553 | else if (nBri > pct30 && nDark < pct30) |
mjr | 82:4f6209cb5c33 | 554 | { |
mjr | 82:4f6209cb5c33 | 555 | // overexposed - decrease exposure time a little |
mjr | 82:4f6209cb5c33 | 556 | if (axcTime > 10) |
mjr | 82:4f6209cb5c33 | 557 | axcTime -= 10; |
mjr | 82:4f6209cb5c33 | 558 | else |
mjr | 82:4f6209cb5c33 | 559 | axcTime = 0; |
mjr | 82:4f6209cb5c33 | 560 | } |
mjr | 86:e30a1f60f783 | 561 | #endif |
mjr | 86:e30a1f60f783 | 562 | |
mjr | 100:1ff35c07217c | 563 | // don't allow the exposure time to go below 0 or over 2.5ms |
mjr | 86:e30a1f60f783 | 564 | if (int(axcTime) < 0) |
mjr | 86:e30a1f60f783 | 565 | axcTime = 0; |
mjr | 86:e30a1f60f783 | 566 | if (axcTime > 2500) |
mjr | 86:e30a1f60f783 | 567 | axcTime = 2500; |
mjr | 82:4f6209cb5c33 | 568 | } |
mjr | 82:4f6209cb5c33 | 569 | |
mjr | 87:8d35c74403af | 570 | #if 0 |
mjr | 87:8d35c74403af | 571 | // convert a reflected Gray code value (up to 16 bits) to binary |
mjr | 87:8d35c74403af | 572 | static inline int grayToBin(int grayval) |
mjr | 82:4f6209cb5c33 | 573 | { |
mjr | 87:8d35c74403af | 574 | int temp = grayval ^ (grayval >> 8); |
mjr | 87:8d35c74403af | 575 | temp ^= (temp >> 4); |
mjr | 87:8d35c74403af | 576 | temp ^= (temp >> 2); |
mjr | 87:8d35c74403af | 577 | temp ^= (temp >> 1); |
mjr | 87:8d35c74403af | 578 | return temp; |
mjr | 82:4f6209cb5c33 | 579 | } |
mjr | 87:8d35c74403af | 580 | #endif |
mjr | 87:8d35c74403af | 581 | |
mjr | 87:8d35c74403af | 582 | // bar code starting pixel offset |
mjr | 87:8d35c74403af | 583 | int startOfs; |
mjr | 82:4f6209cb5c33 | 584 | }; |
mjr | 82:4f6209cb5c33 | 585 | |
mjr | 82:4f6209cb5c33 | 586 | #endif |