tessl install tessl/github-zxing-cpp--zxing-cpp@2.3.0Open-source, multi-format linear/matrix barcode image processing library implemented in C++
This document covers challenging scenarios, edge cases, and troubleshooting for ZXing-C++.
// 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
}
}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;
}
};#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);
}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;
}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;
}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;
}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;
}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";
}
};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;
}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());
}
}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;
}
};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;
}
};#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;
}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();
}
}
};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";
}