summaryrefslogtreecommitdiffstats
path: root/iphone/doc/optimizing.rst
blob: a823fd60e692d7ae48260095c7b87244d78577be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
Optimizing the Reader
=====================

As good as the iPhone performance is for a mobile device, the reality from an
image processing perspective is that it represents a lower performance target.
While the default configuration of the iPhone reader has been carefully tuned
for the general case, you can often obtain much better results if you optimize
for your specific application.

.. note::

   Performance is a complex topic.  The only way to tune performance is by
   changing settings and comparing measured results.  If you are not
   comfortable with the concepts presented here, it is recommended that you
   leave the settings at the defaults and avoid compromising reliability.

Performance of the barcode reader is generally evaluated by two factors:

* The **latency** - how quickly it "recognizes" a barcode.  Specifically this
  is the time from when the user puts a barcode in the frame or selects an
  image until a response is indicated back to the them.

* The **reliability** - it does not matter how quickly an image is scanned if
  an incorrect result is returned.  That may seem obvious, but bad decodes
  *are* possible and you need to keep this in mind when changing settings that
  affect the reliability of the reader.

Basically our goal is to optimize the latency without sacrificing reliability.
There are several factors that contribue to latency:

* The **quality** of the barcode image.  Quality includes the available
  resolution, focus, lighting, noise, etc.  We have more control over some of
  these than others.

* The **camera**.  When scanning from the camera, the time for the
  autoexposure and autofocus to converge on an image that can be decoded is a
  significant contribution to the overall latency.

* The **frame rate** of the reader - this translates to the time it takes the
  scanner to process an image.

* The **effort level** of the reader - some of the available settings control
  "how hard" the reader tries to find barcodes in each frame.

* The **delegate latency** - the time spent in your application after a
  barcode has been detected until the user is notified.

Most of these factors are interrelated.  We will discuss those we can control
in detail, as well the settings you use to affect them.  Then we will provide
a few specific case examples.


Measuring Performance
---------------------

Subjective response times are a good place to start (does it "feel" responsive
to you?), and possibly the only way to evaluate the overall experience, but to
compare incremental changes to interrelated settings and have meaningful
performance discussions with others, we need a more quantitative approach.

The :func:`mach_absolute_time` function is a good tool for accurately
measuring small delays.  Research this function and learn how to apply it.  As
when measuring any real-world value, keep in mind that some variance is to be
expected - even if you perform exactly the same operation multiple times, you
will not see exactly the same measurement.  You should collect several
samples, discard any obvious outliers, and average the remaining measurements.

One way that the overall reader latency may be evaluated is by manually
marking the time when the barcode is presented to the reader.  Add a control
to your overlay that captures the start time when tapped and compare this to
the end time, just before your delegate returns.

The reader continually monitors the frame rate at which it is running.  The
measured value may be displayed for debugging purposes by enabling the
:member:`~ZBarReaderView::showsFPS` property.  The readertest example does
this and also provides control over many of the available settings, so you can
quickly test how each setting affects the frame rate.  You should target your
optimization efforts to achieve a frame rate of at least 8-10fps, although
12-15fps is preferable.

You can measure the latency of your delegate using :func:`mach_absolute_time`.
The measured value should be less than about 100ms, the smaller the better, to
avoid noticeable lag.

The readertest is a good tool for testing the performance of the reader.  You
can tune the settings appropriately for your application and evaluate the
effect each change has on the performance.


Delegate Latency
----------------

This latency contributor is the easiest for you to effect (and sometimes the
easiest to overlook).  Your delegate method should update the interface -
dismiss the controller or update your overlay to indicate success - and
*nothing* else.  All other processing should be deferred until after the
animations have started.


Image Quality
-------------

Resolution
^^^^^^^^^^

One might think that "more is better" in terms of resolution, but this is not
necessarily the case.  Given average image quality, the ideal resolution for
scanning is right around three pixels per barcode "module" (the width of the
smallest bar or space).  Note that this measure is not an absolute image size
or even a measure of the physical dimensions represented by a pixel sample, it
*only* describes the sampled size of the barcode in the image.

As the resolution decreases below about two pixels per module, edge fidelity
is lost and the bars and spaces start to merge together, making it impossible
(for this library) to scan.  This affects the density (feature size) and
maximum size (data capacity) of the barcodes that can be detected.
Conversely, as the resolution increases above about 4 pixels per module, noise
can interfere with the edge detection and images will take longer to process.

Other quality factors, such as poor focus, bad lighting or even excessive
noise, can increase (or decrease) the resolution requirement.

When scanning from the camera, the reader defaults to 640x480, which is good
for most applications.  On newer devices, you can increase this using a capture
:member:`~ZBarReaderView::session` preset.  Some older devices do not have a
higher resolution option available.

For scanning images, you can use
:member:`~ZBarReaderController::maxScanDimension` to control the scaled size
of the converted image, or resort to converting them yourself.

If you want to read long linear barcodes or dense 2-D symbols, you will
probably want to increase the resolution by adjusting these settings.

Keep in mind that more pixels will take longer to scan, refer to the `frame
rate`_ discussion for ways to compensate.

Focus
^^^^^

Ideally we would fix the focus at a calculated optimum distance and optimize
the aperture selection to maximize the depth of field.  Unfortunately the APIs
do not currently give us control over any of these settings, the best we can
do (as of iOS 4) is continuous auto-focus mode - this mode is configured by
the reader automatically.  It can still take the device as long as 1-2 seconds
to find the appropriate macro focus setting, but again, there is currently no
way to reduce this delay.

Lighting and Exposure
^^^^^^^^^^^^^^^^^^^^^

An image that is too bright or overexposed can completely wash out any
barcodes.  An image that is too dark or underexposed will not provide
sufficient contrast for the scanner.  Low light levels also tend to produce
noisier images, possibly because the driver uses a faster "ISO" setting to
compensate for the lighting.

The camera defaults to continuous automatic exposure and white balance.  Since
there are no other useful values, the reader leaves these unchanged from their
default setting.

For some devices, the "torch" can be enabled to provide additional
illumination for the camera in low-light conditions.  The reader sets the
torch to automatic by default, so it should turn on only when needed...
There have been some reports that the torch turns on inappropriately, washing
out the image.  If you find that this occurs, you should instead set the
:member:`~ZBarReaderView::torchMode` property of the :class:`ZBarReaderView`
to ``Off``.

For scanning images from another source, you are again stuck with the
available image quality.  If you have any control over the image source, you
should do what you can to fix quality problems there.

Noise
^^^^^

Some level of noise is filtered by the reader, but excessive noise levels
create additional edges in the image which corrupt barcodes and increase
scanning time (decreasing the frame rate).

As mentioned with `lighting and exposure`_, noise mostly becomes a problem
when the light-level is too low, but high-resolution images may also increase
exposure to sensor noise.

We compensate for noise by *reducing* the `resolution`_ from the sensor
maximum.  Scaling the image down has the effect of averaging several pixels
into one value, filtering out the high-frequency noise component.


Frame Rate
----------

The time it takes to scan and decode an image/frame is roughly proportional to
the number of pixels that are processed.  The number and type of enabled
symbologies and image noise can also affect the processing time.

We have several knobs available that affect the frame rate.  Most of these are
geared toward reducing the number of image pixels that are scanned.

Decrease the Resolution
^^^^^^^^^^^^^^^^^^^^^^^

Adjusting the resolution of the image is an easy way to quickly reduce the
number of pixels.  Smaller images also mean there is less data to carry
around, which helps performance in other ways.  For example, reducing each
image dimension by 30% (eg, from 640x480 to 448x336) will about double the
speed of the reader (to a point).  [FIXME verify!]

Adjusting the resolution is `described above <resolution>`_.  As mentioned
there, reducing the resolution will negatively impact the minimum feature size
and maximum barcode size that can be scanned, but it will help filter noise.

Crop the Scan Region
^^^^^^^^^^^^^^^^^^^^

It may not always be necessary for an application to scan all the way to the
edges of the image.  By cropping the scan area, you can get most of the
benefits of reduced resolution without sacrificing the minimum feature size.
Cropping will also not affect image noise, but similar to decreasing the
resolution, it does affect the maximum size barcode that can be scanned.

For all cases you set the crop rectangle
:class:`~ZBarReaderViewController::scanCrop` property.  Note that the
rectangle provided to the controller is *normalized* across image size and
rotation.  This means that the coordinates range from 0 to 1 and the axes will
be arranged such that the x-axis of the crop rectangle corresponds to the
major (longer) image axis.

Your interface will typically need to indicate the cropped scan area to the
user with visual queues.  Use the
:class:`~ZBarReaderViewController::cameraOverlayView` to provide this.

By default, the :class:`ZBarReaderView` recognizes a pinch gesture to
digitally zoom the preview around the center of the image.  This zoom does not
affect the resolution of the image, but it does crop the scan region to the
visible area.  You can also disable the pinch gesture and set the
:class:`~ZBarReaderView::zoom` programmatically.

Limit the Scan Density
^^^^^^^^^^^^^^^^^^^^^^

The scanner works by making scan passes across the pixel rows and colums of
the image.  The density of the passes is configured at the scanner as a pixel
stride for each axis.  ``ZBAR_CFG_Y_DENSITY`` (``ZBAR_CFG_X_DENSITY``)
controls the number of pixel rows (columns) that are skipped between
successive horizontal (vertical) scan passes.  (Note that "density" is really
not a good name for the configuration settings... "stride" might be more
appropriate.)

Decreasing the scan density (by increasing the stride setting) is a great way
to limit the processing (increasing the frame rate) without sacrificing scan
resolution - each scan pass is still made at full image resolution, there are
just fewer passes (less redundancy).

Setting the stride value to 0 completely disables scanning in that direction.
This is very useful when reading linear codes with a visual alignment guide -
scanning parallel to the bars is a waste of cycles which may be better applied
to support higher resolution or increased density of scans across the symbol.
Note that some 2-D symbologies (QR Code) require scans in both directions.

Setting the stride to a very large value will generate a single scan pass
through the center of the image.  Note that some symbologies will not be
detected without multiple successful passes; it is usually better to combine
this setting with cropping to generate a number of closely clustered scan
passes in the target area.

Note that the density also affects the aspect ratio and rotation that can be
tolerated.  If you set it too large, some barcodes will become more difficult
to read.

In general, 2 to 4 is a good target for the stride setting, unless you have
very high or low resolution images.

Disable unused symbologies
^^^^^^^^^^^^^^^^^^^^^^^^^^

Limiting the symbologies to the set of interest should provide a small
performance boost.  It also improves decode reliability - it is impossible to
receive an incorrect or unexpected decode result from a symbology that is
disabled.

The reader does support full auto-discrimination among the supported
symbologies, but with all of them enabled you may need to compensate elsewhere
to get a good frame rate.

For example, if you are only interested in QR codes, disable the others.  The
robust way to do this is by disabling *all* symbologies and then reenabling
only those you want.  This helps isolate you from encountering new symbologies
that may be added in future versions of the library until you are ready to
handle them::

   [scanner setSymbology: 0
            config: ZBAR_CFG_ENABLE
            to: 0];
   [scanner setSymbology: ZBAR_QRCODE
            config: ZBAR_CFG_ENABLE
            to: 1];

Even if you would like your application to support multiple symbologies, you
may consider if there is a way to limit the enabled subset based on the
scanning context, etc...


Examples
--------

These examples demonstrate several scenarios for scanning from the camera with
automatic capture.  You can try them yourself using the readertest.  For each
example, start with the default settings (by tapping the
``ZBarReaderViewController`` class), then enable continuous mode and the
custom overlay (by disabling
:member:`~ZBarReaderViewController::showsZBarControls`).  You should also use
a release build and avoid running in the debugger.

Frame rates are approximate, measured on an iPhone 3GS running iOS 4.0.1 in a
well lit room.  Two measurements are taken for each sample: the rate with the
camera pointed at a blank white page such that it fills the frame, and the
rate while continuously decoding the provided example.  For best results, it
is recommended that you print the examples rather than scanning them from the
screen.

For reference, the base frame rates with default settings are 12fps for a
blank white page, 7.5fps for this `basic EAN symbol`_ and 2.2fps for this
`basic QR symbol`_.

.. _`basic EAN symbol`:
   http://zbar.sf.net/test/ean13/9876543210128.png
.. _`basic QR symbol`:
   http://chart.apis.google.com/chart?cht=qr&chs=512x512&chl=http://zbar.sf.net/iphone

Long Linear Symbols
^^^^^^^^^^^^^^^^^^^

For this example, we will use a relatively `long Code 128 barcode`_.

.. _`long Code 128 barcode`:
   http://zbar.sf.net/test/code128/ALPHA.png

While it should be possible to read this symbol with the default settings, you
may notice that it is not very reliable.  You will have to stretch the symbol
across the entire screen, and even then the default settings will only give
you about 1.6 pixels per module, well below the ideal target of 3.  To improve
these results, we want to maximize scanning resolution for the long image
axis.

1. Disable the default zoom/crop - zoom all the way out by hitting "Scan" and
   pinching the preview; the frame rate immediately drops to 8fps / 4.8fps.

We should compensate for this reduction in the frame rate:

2. Crop the image to a long, skinny rectangle - set the
   :member:`~ZBarReaderViewController::scanCrop` setting to
   ``{{0, 0.3}, {1, 0.4}}``;  The frame rate jumps up to 18fps / 8.7fps.

3. Disable scans across the short image axis - set the ``CFG_X_DENSITY``
   setting to 0.  The frame rate goes all the way to 30fps / 13fps.

Since we have plenty of margin with the frame rate, we can minimize the total
decode latency by performing more scan passes through the symbol:

4. Increase the scan density - set the ``CFG_Y_DENSITY`` setting to 1 (13.5fps
   / 5fps) or 2 (24fps / 9fps).

You should now be able to quickly and reliably decode long linear symbols.

If have a newer device, you may also try increasing the resolution to support
even longer symbols.  You may have to compensate elsewhere to bring the frame
rate back to a reasonable level.

High Density QR Symbols
^^^^^^^^^^^^^^^^^^^^^^^

For this example we will use a `version 29 QR Code symbol`_.

.. _`version 29 QR Code symbol`:
   http://www.qrcomic.com/images/5.png

In this case we still want to maximize the resolution, but we also need to
increase the scan density to reliably pick up the small finder patterns:

1. Maximize scan density in both directions - set the ``CFG_X_DENSITY`` and
   ``CFG_Y_DENSITY`` settings both to 1.  You should be able to scan the symbol
   now, although the frame rate drops to 4.5fps / 1fps

2. Disable the default zoom/crop - zoom all the way out by hitting "Scan" and
   pinching the preview; the frame rate drops further to 3fps / 0.7fps

We can compensate somewhat for the reduced frame rate:

3. Crop the image to a square - set ``scanCrop`` to ``{{0.125, 0}, {.75, 1}}``.
   This boosts the frame rate slightly to 3.7fps / 0.75fps.

4. Disable linear symbologies - set the symbologies such that only QR Code is
   enabled (4fps / 1fps)

Even though the frame rate is still pretty bad, the QR recognition latency
should be acceptable.

If have an iPhone 4, you may also try increasing the resolution to support
even denser QR symbols. You may have to compensate elsewhere to bring the
frame rate back to a reasonable level.

Small DataBar Symbols
^^^^^^^^^^^^^^^^^^^^^

For this example we will use a `DataBar symbol`_ printed with a small feature
size, typical of the stickers used to tag produce.  Scale it when printing
such that the printed dimensions are about 1cm square.  This symbol should
scan with the default settings, but we will attempt to optimize the scan
latency for this case.

.. _`DataBar symbol`:
   http://zbar.sf.net/test/databar/0109876543210128-so.png

As well as high barcode resolution, we also want high density passes in both
directions to minimize sensitivity to rotation:

1. Maximize scan density in both directions - set the ``CFG_X_DENSITY`` and
   ``CFG_Y_DENSITY`` settings both to 1.  The frame rate drops to 4.5fps /
   3fps.

Compensate for the reduction in frame rate by zooming in on the small symbol,
which crops the scanned image.  Zooming also helps the user see the small
barcode:

2. Zoom all the way in - hit "Scan" and un-pinch the preview.  The frame rate
   recovers to 11fps / 6.2fps.

3. Crop the image to a square - set ``scanCrop`` to ``{{0.125, 0}, {0.75, 1}}``
   (14fps / 7.5fps)

4. Disable all symbologies except DataBar and DataBar Expanded (14.5fps / 9fps)

The reader should now be very sensitive to DataBar, even when scanned at an
angle.