Skip to content

Prototype for supporting multiple binarizers #500

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/BitHacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ inline int HighestBitSet(uint32_t v)
return 31 - NumberOfLeadingZeros(v);
}

template <typename Int>
inline Int FilterLowestBit(Int v)
{
return v & ~(v - 1);
}

// shift a whole array of bits by offset bits to the right (thinking of the array as a contiguous stream of bits
// starting with the LSB of the first int and ending with the MSB of the last int, this is actually a left shift)
template <typename T>
Expand Down
13 changes: 13 additions & 0 deletions core/src/DecodeHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,18 @@
#include "DecodeHints.h"

namespace ZXing {
BinarizerV2 UpgradeBinarizer(Binarizer binarizer){
switch(binarizer){
case Binarizer::BoolCast:
return BinarizerV2::BoolCast;
case Binarizer::FixedThreshold:
return BinarizerV2::FixedThreshold;
case Binarizer::GlobalHistogram:
return BinarizerV2::GlobalHistogram;
case Binarizer::LocalAverage:
return BinarizerV2::LocalAverage;
}

return BinarizerV2::None;
}
} // ZXing
18 changes: 18 additions & 0 deletions core/src/DecodeHints.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

#include "BarcodeFormat.h"
#include "CharacterSet.h"
#include "Flags.h"

#include <cstdint>
#include <string_view>
#include <utility>

Expand All @@ -30,6 +32,19 @@ enum class Binarizer : unsigned char // needs to be unsigned for the bitfield be
BoolCast, ///< T = 0, fastest possible
};

enum class BinarizerV2 : uint32_t
{
None = 0,
LocalAverage = (1 << 0), ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer)
GlobalHistogram = (1 << 1), ///< T = valley between the 2 largest peaks in the histogram (per line in linear case)
FixedThreshold = (1 << 2), ///< T = 127
BoolCast = (1 << 3), ///< T = 0, fastest possible
};

BinarizerV2 UpgradeBinarizer(Binarizer binarizer);

ZX_DECLARE_FLAGS(BinarizerSequence, BinarizerV2)

enum class EanAddOnSymbol : unsigned char // see above
{
Ignore, ///< Ignore any Add-On symbol during read/scan
Expand Down Expand Up @@ -68,6 +83,7 @@ class DecodeHints
uint8_t _maxNumberOfSymbols = 0xff;
uint16_t _downscaleThreshold = 500;
BarcodeFormats _formats = BarcodeFormat::None;
BinarizerSequence _binarizerSequence = BinarizerV2::None;

public:
// bitfields don't get default initialized to 0 before c++20
Expand Down Expand Up @@ -155,6 +171,8 @@ class DecodeHints
DecodeHints& setCharacterSet(std::string_view v)& { return _characterSet = CharacterSetFromString(v), *this; }
DecodeHints&& setCharacterSet(std::string_view v) && { return _characterSet = CharacterSetFromString(v), std::move(*this); }

ZX_PROPERTY(BinarizerSequence, binarizerSequence, setBinarizerSequence)

#undef ZX_PROPERTY

bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); }
Expand Down
6 changes: 6 additions & 0 deletions core/src/Flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ class Flags

constexpr static Flags all() noexcept { return ~(unsigned(~0) << highestBitSet(Int(Enum::_max))); }

Enum unpackNext() {
Enum res = static_cast<Enum>(BitHacks::FilterLowestBit(i));
setFlag(res, false);
return res;
}

private:
// constexpr static inline Int
// initializer_list_helper(typename std::initializer_list<Enum>::const_iterator it,
Expand Down
73 changes: 45 additions & 28 deletions core/src/ReadBarcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ class LumImagePyramid
}
};

ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hints)
ImageView SetupLumImageView(ImageView iv, LumImage& lum, BinarizerV2 binarizer)
{
if (iv.format() == ImageFormat::None)
throw std::invalid_argument("Invalid image format");

if (hints.binarizer() == Binarizer::GlobalHistogram || hints.binarizer() == Binarizer::LocalAverage) {
if (binarizer == BinarizerV2::GlobalHistogram || binarizer == BinarizerV2::LocalAverage) {
if (iv.format() != ImageFormat::Lum) {
lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())](
const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); });
Expand All @@ -107,13 +107,13 @@ ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hint
return iv;
}

std::unique_ptr<BinaryBitmap> CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv)
std::unique_ptr<BinaryBitmap> CreateBitmap(ZXing::BinarizerV2 binarizer, const ImageView& iv)
{
switch (binarizer) {
case Binarizer::BoolCast: return std::make_unique<ThresholdBinarizer>(iv, 0);
case Binarizer::FixedThreshold: return std::make_unique<ThresholdBinarizer>(iv, 127);
case Binarizer::GlobalHistogram: return std::make_unique<GlobalHistogramBinarizer>(iv);
case Binarizer::LocalAverage: return std::make_unique<HybridBinarizer>(iv);
case BinarizerV2::BoolCast: return std::make_unique<ThresholdBinarizer>(iv, 0);
case BinarizerV2::FixedThreshold: return std::make_unique<ThresholdBinarizer>(iv, 127);
case BinarizerV2::GlobalHistogram: return std::make_unique<GlobalHistogramBinarizer>(iv);
case BinarizerV2::LocalAverage: return std::make_unique<HybridBinarizer>(iv);
}
return {}; // silence gcc warning
}
Expand All @@ -126,34 +126,51 @@ Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints)
Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints)
{
LumImage lum;
ImageView iv = SetupLumImageView(_iv, lum, hints);
MultiFormatReader reader(hints);

if (hints.isPure())
return {reader.read(*CreateBitmap(hints.binarizer(), iv))};
auto binarizers = hints.binarizerSequence();
if(binarizers.empty()){
// Try use the legacy entry
binarizers.setFlag(UpgradeBinarizer(hints.binarizer()));
}

LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor());
if (hints.isPure()){
// No support for multiple binarizers in pure mode.
BinarizerV2 firstBinarizer = binarizers.unpackNext();
ImageView iv = SetupLumImageView(_iv, lum, firstBinarizer);
MultiFormatReader reader(hints);
return {reader.read(*CreateBitmap(firstBinarizer, iv))};
}

Results results;
int maxSymbols = hints.maxNumberOfSymbols();
for (auto&& iv : pyramid.layers) {
auto bitmap = CreateBitmap(hints.binarizer(), iv);
for (int invert = 0; invert <= static_cast<int>(hints.tryInvert()); ++invert) {
if (invert)
bitmap->invert();
auto rs = reader.readMultiple(*bitmap, maxSymbols);
for (auto& r : rs) {
if (iv.width() != _iv.width())
r.setPosition(Scale(r.position(), _iv.width() / iv.width()));
if (!Contains(results, r)) {
r.setDecodeHints(hints);
r.setIsInverted(bitmap->inverted());
results.push_back(std::move(r));
--maxSymbols;
BinarizerV2 currentBinarizer = BinarizerV2::None;
while((currentBinarizer = binarizers.unpackNext()) != BinarizerV2::None){
ImageView iv = SetupLumImageView(_iv, lum, currentBinarizer);

// TODO: If GlobalHistogram and LocalAverage are both specified, only read 1D barcodes once.
MultiFormatReader reader(hints);

LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor());

for (auto&& iv : pyramid.layers) {
auto bitmap = CreateBitmap(currentBinarizer, iv);
for (int invert = 0; invert <= static_cast<int>(hints.tryInvert()); ++invert) {
if (invert)
bitmap->invert();
auto rs = reader.readMultiple(*bitmap, maxSymbols);
for (auto& r : rs) {
if (iv.width() != _iv.width())
r.setPosition(Scale(r.position(), _iv.width() / iv.width()));
if (!Contains(results, r)) {
r.setDecodeHints(hints);
r.setIsInverted(bitmap->inverted());
results.push_back(std::move(r));
--maxSymbols;
}
}
if (maxSymbols <= 0)
return results;
}
if (maxSymbols <= 0)
return results;
}
}

Expand Down