192 lines
5.0 KiB
Go
192 lines
5.0 KiB
Go
package gozxing
|
|
|
|
const (
|
|
LUMINANCE_BITS = 5
|
|
LUMINANCE_SHIFT = 8 - LUMINANCE_BITS
|
|
LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS
|
|
)
|
|
|
|
type GlobalHistogramBinarizer struct {
|
|
source LuminanceSource
|
|
luminances []byte
|
|
buckets []int
|
|
}
|
|
|
|
func NewGlobalHistgramBinarizer(source LuminanceSource) Binarizer {
|
|
return &GlobalHistogramBinarizer{
|
|
source: source,
|
|
luminances: []byte{},
|
|
buckets: make([]int, LUMINANCE_BUCKETS),
|
|
}
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) GetLuminanceSource() LuminanceSource {
|
|
return this.source
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) GetWidth() int {
|
|
return this.source.GetWidth()
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) GetHeight() int {
|
|
return this.source.GetHeight()
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) GetBlackRow(y int, row *BitArray) (*BitArray, error) {
|
|
source := this.GetLuminanceSource()
|
|
width := source.GetWidth()
|
|
if row == nil || row.GetSize() < width {
|
|
row = NewBitArray(width)
|
|
} else {
|
|
row.Clear()
|
|
}
|
|
|
|
this.initArrays(width)
|
|
localLuminances, e := source.GetRow(y, this.luminances)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
localBuckets := this.buckets
|
|
for x := 0; x < width; x++ {
|
|
localBuckets[(localLuminances[x]&0xff)>>LUMINANCE_SHIFT]++
|
|
}
|
|
blackPoint, e := this.estimateBlackPoint(localBuckets)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
if width < 3 {
|
|
// Special case for very small images
|
|
for x := 0; x < width; x++ {
|
|
if int(localLuminances[x]&0xff) < blackPoint {
|
|
row.Set(x)
|
|
}
|
|
}
|
|
} else {
|
|
left := int(localLuminances[0] & 0xff)
|
|
center := int(localLuminances[1] & 0xff)
|
|
for x := 1; x < width-1; x++ {
|
|
right := int(localLuminances[x+1] & 0xff)
|
|
// A simple -1 4 -1 box filter with a weight of 2.
|
|
if ((center*4)-left-right)/2 < blackPoint {
|
|
row.Set(x)
|
|
}
|
|
left = center
|
|
center = right
|
|
}
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) GetBlackMatrix() (*BitMatrix, error) {
|
|
source := this.GetLuminanceSource()
|
|
width := source.GetWidth()
|
|
height := source.GetHeight()
|
|
matrix, e := NewBitMatrix(width, height)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
|
|
// more robust on the blackbox tests than sampling a diagonal as we used to do.
|
|
this.initArrays(width)
|
|
localBuckets := this.buckets
|
|
for y := 1; y < 5; y++ {
|
|
row := height * y / 5
|
|
localLuminances, _ := source.GetRow(row, this.luminances)
|
|
right := (width * 4) / 5
|
|
for x := width / 5; x < right; x++ {
|
|
pixel := localLuminances[x] & 0xff
|
|
localBuckets[pixel>>LUMINANCE_SHIFT]++
|
|
}
|
|
}
|
|
blackPoint, e := this.estimateBlackPoint(localBuckets)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
// We delay reading the entire image luminance until the black point estimation succeeds.
|
|
// Although we end up reading four rows twice, it is consistent with our motto of
|
|
// "fail quickly" which is necessary for continuous scanning.
|
|
localLuminances := source.GetMatrix()
|
|
for y := 0; y < height; y++ {
|
|
offset := y * width
|
|
for x := 0; x < width; x++ {
|
|
pixel := int(localLuminances[offset+x] & 0xff)
|
|
if pixel < blackPoint {
|
|
matrix.Set(x, y)
|
|
}
|
|
}
|
|
}
|
|
|
|
return matrix, nil
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) CreateBinarizer(source LuminanceSource) Binarizer {
|
|
return NewGlobalHistgramBinarizer(source)
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) initArrays(luminanceSize int) {
|
|
if len(this.luminances) < luminanceSize {
|
|
this.luminances = make([]byte, luminanceSize)
|
|
}
|
|
for x := 0; x < LUMINANCE_BUCKETS; x++ {
|
|
this.buckets[x] = 0
|
|
}
|
|
}
|
|
|
|
func (this *GlobalHistogramBinarizer) estimateBlackPoint(buckets []int) (int, error) {
|
|
// Find the tallest peak in the histogram.
|
|
numBuckets := len(buckets)
|
|
maxBucketCount := 0
|
|
firstPeak := 0
|
|
firstPeakSize := 0
|
|
for x := 0; x < numBuckets; x++ {
|
|
if buckets[x] > firstPeakSize {
|
|
firstPeak = x
|
|
firstPeakSize = buckets[x]
|
|
}
|
|
if buckets[x] > maxBucketCount {
|
|
maxBucketCount = buckets[x]
|
|
}
|
|
}
|
|
|
|
// Find the second-tallest peak which is somewhat far from the tallest peak.
|
|
secondPeak := 0
|
|
secondPeakScore := 0
|
|
for x := 0; x < numBuckets; x++ {
|
|
distanceToBiggest := x - firstPeak
|
|
// Encourage more distant second peaks by multiplying by square of distance.
|
|
score := buckets[x] * distanceToBiggest * distanceToBiggest
|
|
if score > secondPeakScore {
|
|
secondPeak = x
|
|
secondPeakScore = score
|
|
}
|
|
}
|
|
|
|
// Make sure firstPeak corresponds to the black peak.
|
|
if firstPeak > secondPeak {
|
|
firstPeak, secondPeak = secondPeak, firstPeak
|
|
}
|
|
|
|
// If there is too little contrast in the image to pick a meaningful black point, throw rather
|
|
// than waste time trying to decode the image, and risk false positives.
|
|
if secondPeak-firstPeak <= numBuckets/16 {
|
|
return 0, NewNotFoundException()
|
|
}
|
|
|
|
// Find a valley between them that is low and closer to the white peak.
|
|
bestValley := secondPeak - 1
|
|
bestValleyScore := -1
|
|
for x := secondPeak - 1; x > firstPeak; x-- {
|
|
fromFirst := x - firstPeak
|
|
score := fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x])
|
|
if score > bestValleyScore {
|
|
bestValley = x
|
|
bestValleyScore = score
|
|
}
|
|
}
|
|
|
|
return bestValley << LUMINANCE_SHIFT, nil
|
|
}
|