quagga2 icon indicating copy to clipboard operation
quagga2 copied to clipboard

BoxFromPatches sometimes returns a larger box than necessary

Open TonyKhorouzan opened this issue 3 years ago • 3 comments

In boxFromPatches, all of the boxes are rotated by the average radian of the boxes. The bounding box is then determined by finding the minimum and maximum X and Y values containing all rotated boxes. The currently code starts the minimum and maximum values using the image size, which would normally be conservative values that would always be improved on. However, the rotation of the boxes can place some points of them outside those minimum and maximum values. It is better to start the minimum and maximum values using an actual rotated point.

In practice, this is a rare event.

In barcode_locator.js in method boxFromPatches at line 112:

    let minx = _binaryImageWrapper.size.x;
    let miny = _binaryImageWrapper.size.y;
    let maxx = -_binaryImageWrapper.size.x;
    let maxy = -_binaryImageWrapper.size.y;
... (omitted code) ...
    // find bounding box
    for (i = 0; i < patches.length; i++) {
        patch = patches[i];
        for (j = 0; j < 4; j++) {
            if (patch.box[j][0] < minx) {
                minx = patch.box[j][0];
            }
            if (patch.box[j][0] > maxx) {
                maxx = patch.box[j][0];
            }
            if (patch.box[j][1] < miny) {
                miny = patch.box[j][1];
            }
            if (patch.box[j][1] > maxy) {
                maxy = patch.box[j][1];
            }
        }
    }

could be changed to:

  let minx;
  let miny;
  let maxx;
  let maxy;
 ... (omitted code unchanged) ...
  // find bounding box
  // Start min and maximum values to the first patches' upper-left coordinate, and improve them if find better values.
  minx = maxx = patches[0].box[0][0];
  miny = maxy = patches[0].box[0][1];
  for (i = 0; i < patches.length; i++) {
... (omitted code unchanged) ...

An example makes this more clear:

_binaryImageWrapper.size.x = 480
_binaryImageWrapper.size.y = 240
degrees used to build transformational array = 92.28
radians used to build transformational array = 1.61

Patch before rotation
	Top Left         (x,y): 360,192
	Top Right       (x,y): 384,192
	Bottom Left   (x,y): 360,216
	Bottom Right (x,y): 384,216

Patch after rotation (approximately 90 degrees)
	Top Left          (x,y): -206,352
	Top Right       (x,y): -207,376
	Bottom Left    (x,y): -230,351
	Bottom Right (x,y): -231,375

patches[0].box[0][0] before rotation	 360
                                     after rotation	        -206

patches[0].box[0][1] before rotation    192
                                     after rotation	     352

The original code determines miny as 240, which does not get improved as it checks all rotated points. However, the true minimum is 351. Starting with the first coordinate's Y at 352 with the new code will result in a correct minimum Y of 351.

Although the containing box is constructed with coordinates that appear out of range per the original image, the box is then reverse rotated and rescaled.

Other notes about revised code:

  • We can rely upon patches[0] existing as we know patches.length is > 0 at this point (or else a division by zero would occur when calculating overAvg)
  • We can use any of the rotated points for the starting x and y coordinates, as we are iterating through all of them to find the min and max. We choose the top-left as it as a good as any of the points. This also applies to using an actual point for the starting maxx and maxy instead of -size.x and -size.y.

TonyKhorouzan avatar Sep 12 '22 20:09 TonyKhorouzan

OK, so, if I'm reading this correctly, you're saying the current code initializes MinY at the entire image's Y size, but as your patches rotate around, the Y doesn't actually change, because in that example, 240 was too small to begin with?

So, wouldn't the best answer be to initialize both Min at Infinity, and both Max at negative Infinity?

i come up with after a little bit of refactoring

    let minx = Infinity;
    let maxx = -Infinity;
    let miny = Infinity;
    let maxy = -Infinity;
    // find bounding box
    patches.forEach((patch) => {
        for (j = 0; j < 4; j++) {
            minx = Math.min(minx, patch.box[j][0]);
            maxx = Math.max(maxx, patch.box[j][0]);
            miny = Math.min(miny, patch.box[j][1]);
            maxy = Math.max(maxy, patch.box[j][1]);
        }
    });

ericblade avatar Sep 12 '22 20:09 ericblade

Yes, that is correct, initializing to +/- infinity would also work.

I am not a JavaScript developer, so I didn't know that was an option. I am curious what you would get if you were to next divide minx by maxx (infinity/-infinity = ?)

I don't think that would be a solution for the language I ported portions of the code to (C#).

TonyKhorouzan avatar Sep 12 '22 22:09 TonyKhorouzan

division with Infinity or -Infinity returns NaN (Not a Number) .. but that shouldn't be a thing, because there should always be at least one patch with a x/y of less than Infinity.. right?

If you don't have Infinite numbers, you can use a really big number and a really big negative number :)

Anyway, yes, I was trying to make sure that I understood the crux of the problem, so I could be sure that I was solving it correctly when refactoring the code to use more modern features. :)

Thank you!

ericblade avatar Sep 13 '22 08:09 ericblade