Repository: sz3/libcimbar
File: src/lib/cimb_translator/CimbReader.cpp (C++)

Code before:
			cv::cvtColor(img, symbols, cv::COLOR_RGB2GRAY);

		cv::adaptiveThreshold(symbols, symbols, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, blockSize, 0);

		bitbuffer bb(1024*128);
		bitmatrix::mat_to_bitbuffer(symbols, bb.get_writer());
		return bb;
	}
}

CimbReader::CimbReader(const cv::Mat& img, const CimbDecoder& decoder, bool needs_sharpen)
    : _image(img)
    , _cellSize(Config::cell_size() + 2)
    , _positions(Config::cell_spacing(), Config::num_cells(), Config::cell_size(), Config::corner_padding())
    , _decoder(decoder)
    , _good(_image.cols >= Config::image_size() and _image.rows >= Config::image_size())
{
	_grayscale = preprocessSymbolGrid(img, needs_sharpen);
}

CimbReader::CimbReader(const cv::UMat& img, const CimbDecoder& decoder, bool needs_sharpen)
    : CimbReader(img.getMat(cv::ACCESS_READ), decoder, needs_sharpen)
{
}

unsigned CimbReader::read_color(const PositionData& pos)
{
	Cell color_cell(_image, pos.x, pos.y, Config::cell_size(), Config::cell_size());
	return _decoder.decode_color(color_cell);
}

unsigned CimbReader::read(PositionData& pos)
{
	if (done())
		return 0;

	// need coordinate, index, and drift from next position
	auto [i, xy, drift] = _positions.next();
	int x = xy.first + drift.x();
	int y = xy.second + drift.y();
	bitmatrix cell(_grayscale, _image.cols, _image.rows, x-1, y-1);

	unsigned drift_offset = 0;
	unsigned error_distance;
	unsigned bits = _decoder.decode_symbol(cell, drift_offset, error_distance);

	std::pair<int, int> best_drift = CellDrift::driftPairs[drift_offset];
	drift.updateDrift(best_drift.first, best_drift.second);
	_positions.update(i, drift, error_distance);

	pos.i = i;

Review comment (suggestion):
Use the 3 anchor corners of the image to find a reasonable "observed" white value. The anchor pattern should have a white patch in the middle -- if we exacted it properly, it'll be at these locations. We'll use the maximum RGB values we find.

Code after:
		}
		else
			cv::cvtColor(img, symbols, cv::COLOR_RGB2GRAY);

		cv::adaptiveThreshold(symbols, symbols, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, blockSize, 0);

		bitbuffer bb(1024*128);
		bitmatrix::mat_to_bitbuffer(symbols, bb.get_writer());
		return bb;
	}

	void updateMaxColor(std::tuple<float, float, float>& max_color, const cv::Scalar& c)
	{
		std::get<0>(max_color) = std::max(std::get<0>(max_color), static_cast<float>(c[0]));
		std::get<1>(max_color) = std::max(std::get<1>(max_color), static_cast<float>(c[1]));
		std::get<2>(max_color) = std::max(std::get<2>(max_color), static_cast<float>(c[2]));
	}

	std::tuple<float, float, float> calculateWhite(const cv::Mat& img, bool dark)
	{
		std::tuple<float, float, float> bestColor({1, 1, 1});
		if (dark)
		{
			unsigned tl = Config::anchor_size() - 2;
			unsigned br = Config::image_size() - Config::anchor_size() - 2;
			std::array<std::pair<unsigned, unsigned>, 3> anchors = {{ {tl, tl}, {tl, br}, {br, tl} }};
			for (auto [x, y] : anchors)
			{
				cv::Rect crop(x, y, 4, 4);
				cv::Scalar avgColor = cv::mean(img(crop));
				updateMaxColor(bestColor, avgColor);
			}
		}
		else // light
		{
			unsigned tl = (Config::anchor_size() << 1) + 6;
			unsigned br = Config::image_size() - tl - 4;
			std::array<std::pair<unsigned, unsigned>, 4> anchors = {{ {0, tl}, {tl, 0}, {0, br}, {br, 0} }};
			for (auto [x, y] : anchors)
			{
				cv::Rect crop(x, y, 4, 4);
				cv::Scalar avgColor = cv::mean(img(crop));
				updateMaxColor(bestColor, avgColor);
			}
		}
		return bestColor;
	}

	bool updateColorCorrection(const cv::Mat& img, CimbDecoder& decoder)
	{
		std::tuple<float, float, float> white = calculateWhite(img, Config::dark());