quaggaJS: Limiting false positives

During excessive testing, I found that I get about 5 to 10 percent false positives. But I found out – and I do not think that this is documented - if you check the error of the decodedCodes, you will find that by rejection codes with a certain error margin will increase your hit rate 100 percent.

This is the code I use. I take the average error margin. If it is below 0.1 it is fair to assume we detected the code correct.

So far I had no false positives at all while still a very fast detection.

var countDecodedCodes=0, err=0;
$.each(result.codeResult.decodedCodes, function(id,error){
    if (error.error!=undefined) {
        countDecodedCodes++;
        err+=parseFloat(error.error);
    }
});
if (err/countDecodedCodes < 0.1) {
    // correct code detected
} else {
    // probably wrong code
}

Hope this helps!

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 10
  • Comments: 15

Most upvoted comments

Based on @iFlash answer, I made it using median instead of averages.

private _getMedian(arr: number[]): number {
  arr.sort((a, b) => a - b);
  const half = Math.floor( arr.length / 2 );
  if (arr.length % 2 === 1) // Odd length
    return arr[ half ];
  return (arr[half - 1] + arr[half]) / 2.0;
}

// Initializers
private _initDetectedHandler() {
  this.onDetectedHandler = (result) => {
    const errors: number[] = result.codeResult.decodedCodes
      .filter(_ => _.error !== undefined)
      .map(_ => _.error);
    const median = this._getMedian( errors );
    if (median < 0.10)
      // probably correct
    else
      // probably wrong
  };

  Quagga.onDetected( this.onDetectedHandler );
}

During my tests (built-in webcam), I noticed that many times it reads correctly, but its averages were all above 0.1 because some of the errors has a much higher value like 0.3 ..0.4 while others are 0.05. .0.07, thus “pulling” the average up.

Medians represents most of the dataset a bit better. That being said, I still get false positives occasionally. Hit rate of probably 7-8/10, but a faster match than averages.

Its good for me.

let codes = []
function _onDetected(result) {
	codes.push(result.codeResult.code)
	if (codes.length < 3) return
	let is_same_all = false;
	if (codes.every(v => v === codes[0])) {
		is_same_all = true;
	}
	if (!is_same_all) {
		codes.shift()
		return
	}
}

you can try this make an array and select the highest mode value

 function mode(array){
            if(array.length == 0)
                return null;
            var modeMap = {};
            var maxEl = array[0], maxCount = 1;
            for(var i = 0; i < array.length; i++)
            {
                var el = array[i];
                if(modeMap[el] == null)
                    modeMap[el] = 1;
                else
                    modeMap[el]++;
                if(modeMap[el] > maxCount)
                {
                    maxEl = el;
                    maxCount = modeMap[el];
                }
            }
            return maxEl;
        }


  var last_result=[];


            Quagga.onDetected(function (result) {
                var last_code = result.codeResult.code;
                last_result.push(last_code);
                if (last_result.length >20){
                console.log(last_result);
                //when we reached the last scanned object take the most repeated is the correct one
                code = mode(last_result);
                console.log(code +" Is the most valid one");
                  }
            });

Hi,

I too am struggling with error rates. I have tried your code and unfortunately still get errors, the longer the code the more likely there will be an error.

For example scanning the attached code for me sometimes scans correctly, at other times returns garbage within the string such as “6e&n Connery”:

sean-connery-barcode

My own personal sanity check on the bar codes is to dictate a valid format of the returned string, e.g. reject any results that contain non-alphanumeric such as &, #, @, etc. This may work for you, but if you actually want to accept strings containing these characters this workaround is not a valid solution.

I’ve combined your solution with my own, which has reduced my error rate to zero in my use case:

         Quagga.onDetected(function(result) {
                var code = result.codeResult.code;

                if (App.lastResult !== code) {
                        App.lastResult = code;

                        var countDecodedCodes=0, err=0;
                        $.each(result.codeResult.decodedCodes, function(id,error) {
                                if (error.error!=undefined) {
                                        countDecodedCodes++;
                                        err += parseFloat(error.error);
                                }
                        });
                        if (err / countDecodedCodes < 0.1 && sanityCheck(code)) {
                                Quagga.stop();
                                $("#scanModal").modal("hide");
                                $(linked_input).val(code);
                                border_pulse(linked_input);
                        }
                }
        });
        function sanityCheck(s) {
                return s.toUpperCase().match(/^[0-9A-Z\s\-\.\/]+$/);
        }

Hope this helps!

@ericblade Base on @sam-lex answer, I get good results (near 100%) validating errors against two threshold : median et max values

function isValid(result) {
const errors: number[] = result.codeResult.decodedCodes
   .filter(_ => _.error !== undefined)
   .map(_ => _.error);

const median = this._getMedian(errors);

//Good result for code_128 : median <= 0.08 and maxError < 0.1
return !(median > 0.08 || errors.some(err => err > 0.1))
}