The one tricky aspect of the UPC code is accurately gauging the bit rate, but that is facilitated by the fact that each code starts and ends with a sequence of narrow bars and spaces. Once the bit rate has been determined, one can start by converting each "pixel" that matches the previous one to a zero and each pixel that differs into a 1. Each character will start with a 1 and have six bits after it, of which three will be set and three will be clear. There are 64 combinations of six bits, and 20 of them have three ones and three zeroes. Ten of those are used to represent digits on the left half of the code, and ten are used to represent digits on the right.
The one feature of UPC code which makes it difficult to process, and I'm not sure how this was handled, is the possibility of having up to three consecutive zero bits. I would guess that if the inventor of UPC had thought of interleaving pairs of digits, an interleaved 2-of-5 format might have been used instead (though perhaps with other start/end sentinels), since it offers the same data density while only using two widths of bar and space. I suspect that in practice capturing the bit rate for UPC data would not have been a problem for a laser scanner which could use one pass to measure the data rate (originally each code would have always had the same number of bars and spaces, stored in the same number of "pixels", so measuring the time between the start and end sentinels and dividing by the number of pixels would have given the time per bit). Reading UPC is harder for a single-pass handheld wand, but I don't know how common those were expected to be.