or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

tessl/github-zxing-cpp--zxing-cpp

tessl install tessl/github-zxing-cpp--zxing-cpp@2.3.0

Open-source, multi-format linear/matrix barcode image processing library implemented in C++

edge-cases.mddocs/examples/

Edge Cases and Advanced Scenarios

This document covers challenging scenarios, edge cases, and troubleshooting for ZXing-C++.

Damaged or Partially Obscured Barcodes

Using Error Correction

// Enable returnErrors to see partial results
auto options = ZXing::ReaderOptions()
    .setFormats(ZXing::BarcodeFormat::QRCode)
    .setTryHarder(true)
    .setReturnErrors(true);

auto barcodes = ZXing::ReadBarcodes(image, options);

for (const auto& bc : barcodes) {
    if (bc.isValid()) {
        std::cout << "Valid: " << bc.text() << "\n";
    } else if (bc.error().type() == ZXing::Error::Type::Checksum) {
        // Checksum failed but data was decoded
        std::cout << "Warning: Checksum failed\n";
        std::cout << "Unchecked data: " << bc.text() << "\n";
        // Decide whether to use based on risk tolerance
    }
}

Multiple Scan Attempts

class RobustScanner {
public:
    std::optional<std::string> scanWithRetry(
        const ZXing::ImageView& image,
        int maxAttempts = 3
    ) {
        // Try with increasing aggressiveness
        std::vector<ZXing::ReaderOptions> strategies = {
            // Strategy 1: Fast
            ZXing::ReaderOptions()
                .setTryHarder(false)
                .setTryRotate(false),
            
            // Strategy 2: Normal
            ZXing::ReaderOptions()
                .setTryHarder(true)
                .setTryRotate(true),
            
            // Strategy 3: Aggressive
            ZXing::ReaderOptions()
                .setTryHarder(true)
                .setTryRotate(true)
                .setTryInvert(true)
                .setTryDownscale(true)
        };
        
        for (int i = 0; i < std::min(maxAttempts, (int)strategies.size()); ++i) {
            auto barcode = ZXing::ReadBarcode(image, strategies[i]);
            if (barcode.isValid()) {
                return barcode.text();
            }
        }
        
        return std::nullopt;
    }
};

Low Quality Images

Preprocessing with OpenCV

#include <opencv2/opencv.hpp>

ZXing::Barcode scanLowQualityImage(const cv::Mat& input) {
    cv::Mat processed;
    
    // 1. Convert to grayscale
    cv::cvtColor(input, processed, cv::COLOR_BGR2GRAY);
    
    // 2. Increase contrast
    cv::equalizeHist(processed, processed);
    
    // 3. Denoise
    cv::fastNlMeansDenoising(processed, processed);
    
    // 4. Sharpen
    cv::Mat kernel = (cv::Mat_<float>(3,3) <<
        0, -1, 0,
        -1, 5, -1,
        0, -1, 0);
    cv::filter2D(processed, processed, -1, kernel);
    
    // 5. Adaptive threshold (optional)
    // cv::adaptiveThreshold(processed, processed, 255,
    //                       cv::ADAPTIVE_THRESH_GAUSSIAN_C,
    //                       cv::THRESH_BINARY, 11, 2);
    
    auto image = ZXing::ImageView(
        processed.data, processed.cols, processed.rows,
        ZXing::ImageFormat::Lum, processed.step
    );
    
    auto options = ZXing::ReaderOptions()
        .setTryHarder(true)
        .setTryInvert(true);
    
    return ZXing::ReadBarcode(image, options);
}

Multi-Scale Detection

std::optional<ZXing::Barcode> multiScaleScan(const cv::Mat& image) {
    std::vector<double> scales = {1.0, 0.75, 0.5, 1.5, 2.0};
    
    for (double scale : scales) {
        cv::Mat resized;
        cv::resize(image, resized, cv::Size(), scale, scale);
        
        auto zxImage = ZXing::ImageView(
            resized.data, resized.cols, resized.rows,
            ZXing::ImageFormat::Lum, resized.step
        );
        
        auto barcode = ZXing::ReadBarcode(zxImage);
        if (barcode.isValid()) {
            return barcode;
        }
    }
    
    return std::nullopt;
}

Perspective Distortion

Automatic Perspective Correction

cv::Mat correctPerspective(const cv::Mat& image, 
                           const ZXing::Position& position) {
    // Get barcode corners
    std::vector<cv::Point2f> srcPoints = {
        cv::Point2f(position.topLeft().x, position.topLeft().y),
        cv::Point2f(position.topRight().x, position.topRight().y),
        cv::Point2f(position.bottomRight().x, position.bottomRight().y),
        cv::Point2f(position.bottomLeft().x, position.bottomLeft().y)
    };
    
    // Calculate output size
    float width = std::max(
        cv::norm(srcPoints[0] - srcPoints[1]),
        cv::norm(srcPoints[3] - srcPoints[2])
    );
    float height = std::max(
        cv::norm(srcPoints[0] - srcPoints[3]),
        cv::norm(srcPoints[1] - srcPoints[2])
    );
    
    // Define destination points
    std::vector<cv::Point2f> dstPoints = {
        cv::Point2f(0, 0),
        cv::Point2f(width - 1, 0),
        cv::Point2f(width - 1, height - 1),
        cv::Point2f(0, height - 1)
    };
    
    // Calculate perspective transform
    cv::Mat transform = cv::getPerspectiveTransform(srcPoints, dstPoints);
    
    // Apply transform
    cv::Mat corrected;
    cv::warpPerspective(image, corrected, transform, 
                       cv::Size(width, height));
    
    return corrected;
}

Multiple Overlapping Barcodes

Spatial Filtering

std::vector<ZXing::Barcode> filterOverlapping(
    const std::vector<ZXing::Barcode>& barcodes
) {
    std::vector<ZXing::Barcode> filtered;
    
    for (const auto& bc1 : barcodes) {
        bool overlaps = false;
        
        for (const auto& bc2 : filtered) {
            if (ZXing::HaveIntersectingBoundingBoxes(
                bc1.position(), bc2.position()
            )) {
                overlaps = true;
                break;
            }
        }
        
        if (!overlaps) {
            filtered.push_back(bc1);
        }
    }
    
    return filtered;
}

Region-Based Scanning

std::vector<ZXing::Barcode> scanRegions(
    const ZXing::ImageView& image,
    const std::vector<cv::Rect>& regions
) {
    std::vector<ZXing::Barcode> results;
    
    for (const auto& region : regions) {
        auto roi = image.cropped(
            region.x, region.y,
            region.width, region.height
        );
        
        auto barcode = ZXing::ReadBarcode(roi);
        if (barcode.isValid()) {
            // Adjust position to full image coordinates
            auto pos = barcode.position();
            // Offset by region position...
            results.push_back(barcode);
        }
    }
    
    return results;
}

Structured Append Sequences

Collecting Multi-Symbol Sequences

class SequenceCollector {
    struct SequenceState {
        std::vector<ZXing::Barcode> symbols;
        std::chrono::steady_clock::time_point lastUpdate;
    };
    
    std::map<std::string, SequenceState> sequences;
    std::chrono::seconds timeout{30};
    
public:
    void addSymbol(const ZXing::Barcode& barcode) {
        if (!barcode.isPartOfSequence()) {
            // Single barcode, process immediately
            processComplete(barcode);
            return;
        }
        
        std::string id = barcode.sequenceId();
        sequences[id].symbols.push_back(barcode);
        sequences[id].lastUpdate = std::chrono::steady_clock::now();
        
        // Check if complete
        if (isComplete(id)) {
            auto merged = ZXing::MergeStructuredAppendSequence(
                sequences[id].symbols
            );
            
            if (merged.isValid()) {
                processComplete(merged);
                sequences.erase(id);
            }
        }
    }
    
    void cleanup() {
        auto now = std::chrono::steady_clock::now();
        
        for (auto it = sequences.begin(); it != sequences.end(); ) {
            if (now - it->second.lastUpdate > timeout) {
                onTimeout(it->first);
                it = sequences.erase(it);
            } else {
                ++it;
            }
        }
    }
    
private:
    bool isComplete(const std::string& id) {
        const auto& symbols = sequences[id].symbols;
        if (symbols.empty()) return false;
        
        int expectedSize = symbols[0].sequenceSize();
        if (symbols.size() != expectedSize) return false;
        
        // Check all indices present
        std::set<int> indices;
        for (const auto& sym : symbols) {
            indices.insert(sym.sequenceIndex());
        }
        
        return indices.size() == expectedSize;
    }
    
    void processComplete(const ZXing::Barcode& barcode) {
        // Process complete barcode
        std::cout << "Complete: " << barcode.text() << "\n";
    }
    
    void onTimeout(const std::string& id) {
        std::cerr << "Sequence timeout: " << id << "\n";
    }
};

Character Encoding Issues

Handling Unknown Encodings

std::string decodeWithFallback(const ZXing::Barcode& barcode) {
    // Try default decoding
    std::string text = barcode.text();
    
    // If content type is unknown ECI
    if (barcode.contentType() == ZXing::ContentType::UnknownECI) {
        // Get raw bytes
        const auto& bytes = barcode.bytes();
        
        // Try common encodings
        std::vector<ZXing::CharacterSet> fallbacks = {
            ZXing::CharacterSet::UTF8,
            ZXing::CharacterSet::ISO8859_1,
            ZXing::CharacterSet::Shift_JIS,
            ZXing::CharacterSet::GB2312
        };
        
        for (auto charset : fallbacks) {
            // Attempt decode with charset
            // (requires custom decoding logic)
            auto decoded = tryDecode(bytes, charset);
            if (decoded.has_value()) {
                return *decoded;
            }
        }
        
        // Fall back to hex representation
        return barcode.text(ZXing::TextMode::Hex);
    }
    
    return text;
}

Binary Content Handling

void processBinaryBarcode(const ZXing::Barcode& barcode) {
    if (barcode.contentType() == ZXing::ContentType::Binary) {
        const auto& bytes = barcode.bytes();
        
        // Process as binary data
        std::vector<uint8_t> data(bytes.begin(), bytes.end());
        
        // Example: Save to file
        std::ofstream file("barcode_data.bin", std::ios::binary);
        file.write(reinterpret_cast<const char*>(data.data()), 
                   data.size());
    }
}

Performance Optimization

Adaptive Quality Settings

class AdaptiveScanner {
    int consecutiveFailures = 0;
    ZXing::ReaderOptions options;
    
public:
    AdaptiveScanner() {
        options.setTryHarder(false)
               .setTryRotate(false);
    }
    
    std::optional<ZXing::Barcode> scan(const ZXing::ImageView& image) {
        auto barcode = ZXing::ReadBarcode(image, options);
        
        if (barcode.isValid()) {
            consecutiveFailures = 0;
            // Reduce quality for speed
            if (options.tryHarder()) {
                options.setTryHarder(false);
            }
            return barcode;
        }
        
        // Increase quality after failures
        consecutiveFailures++;
        if (consecutiveFailures > 3 && !options.tryHarder()) {
            options.setTryHarder(true);
        }
        if (consecutiveFailures > 5 && !options.tryRotate()) {
            options.setTryRotate(true);
        }
        
        return std::nullopt;
    }
};

Frame Skipping for Video

class VideoScanner {
    int frameCount = 0;
    int skipFrames = 2;  // Process every 3rd frame
    std::optional<ZXing::Barcode> lastResult;
    
public:
    std::optional<ZXing::Barcode> processFrame(const cv::Mat& frame) {
        frameCount++;
        
        // Skip frames for performance
        if (frameCount % (skipFrames + 1) != 0) {
            return lastResult;
        }
        
        // Downscale for faster processing
        cv::Mat small;
        cv::resize(frame, small, cv::Size(), 0.5, 0.5);
        
        cv::Mat gray;
        cv::cvtColor(small, gray, cv::COLOR_BGR2GRAY);
        
        auto image = ZXing::ImageView(
            gray.data, gray.cols, gray.rows,
            ZXing::ImageFormat::Lum, gray.step
        );
        
        auto options = ZXing::ReaderOptions()
            .setFormats(ZXing::BarcodeFormat::QRCode)
            .setTryRotate(false);  // Assume camera is upright
        
        auto barcode = ZXing::ReadBarcode(image, options);
        
        if (barcode.isValid()) {
            lastResult = barcode;
            skipFrames = std::max(0, skipFrames - 1);  // Process more often after success
        } else {
            skipFrames = std::min(5, skipFrames + 1);  // Process less often after failure
        }
        
        return lastResult;
    }
};

Thread Safety and Concurrency

Parallel Batch Processing

#include <thread>
#include <future>

std::vector<ZXing::Barcode> parallelScan(
    const std::vector<ZXing::ImageView>& images
) {
    std::vector<std::future<ZXing::Barcode>> futures;
    
    for (const auto& image : images) {
        futures.push_back(std::async(std::launch::async, [&image]() {
            return ZXing::ReadBarcode(image);
        }));
    }
    
    std::vector<ZXing::Barcode> results;
    for (auto& future : futures) {
        results.push_back(future.get());
    }
    
    return results;
}

Thread Pool

class ScannerThreadPool {
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop = false;
    
public:
    ScannerThreadPool(size_t numThreads) {
        for (size_t i = 0; i < numThreads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    
                    {
                        std::unique_lock<std::mutex> lock(queueMutex);
                        condition.wait(lock, [this] {
                            return stop || !tasks.empty();
                        });
                        
                        if (stop && tasks.empty()) return;
                        
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    
                    task();
                }
            });
        }
    }
    
    template<class F>
    auto enqueue(F&& f) -> std::future<typename std::result_of<F()>::type> {
        using return_type = typename std::result_of<F()>::type;
        
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::forward<F>(f)
        );
        
        std::future<return_type> res = task->get_future();
        
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            tasks.emplace([task]() { (*task)(); });
        }
        
        condition.notify_one();
        return res;
    }
    
    ~ScannerThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& worker : workers) {
            worker.join();
        }
    }
};

Debugging and Diagnostics

Detailed Error Logging

void logDetailedError(const ZXing::Barcode& barcode,
                     const ZXing::ImageView& image) {
    if (barcode.isValid()) return;
    
    std::cerr << "=== Barcode Read Failed ===\n";
    std::cerr << "Error Type: " << static_cast<int>(barcode.error().type()) << "\n";
    std::cerr << "Error Message: " << barcode.error().msg() << "\n";
    std::cerr << "Error Location: " << barcode.error().location() << "\n";
    std::cerr << "Image Size: " << image.width() << "x" << image.height() << "\n";
    std::cerr << "Image Format: " << static_cast<int>(image.format()) << "\n";
    
    // Log image statistics
    const uint8_t* data = image.data();
    int pixelCount = image.width() * image.height();
    
    int sum = 0;
    int min = 255, max = 0;
    for (int i = 0; i < pixelCount; ++i) {
        uint8_t pixel = data[i];
        sum += pixel;
        min = std::min(min, (int)pixel);
        max = std::max(max, (int)pixel);
    }
    
    std::cerr << "Image Stats:\n";
    std::cerr << "  Average: " << (sum / pixelCount) << "\n";
    std::cerr << "  Min: " << min << "\n";
    std::cerr << "  Max: " << max << "\n";
    std::cerr << "  Contrast: " << (max - min) << "\n";
}

Next Steps

  • Real-World Scenarios - Common use cases
  • Quick Start Guide - Basic usage
  • API Reference - Detailed documentation