eyemCodeDetector.cpp 8.4 KB
#include "eyemCodeDetector.h"
#include "yoloWrapper.h"
#include "barcodeDetector.h"

class CodeDetector::Impl {
public:
	Impl() {}
	~Impl() {}

	//目标检测
	std::vector<cv::Rect> detect(const cv::Mat& img);

	//二维码解码
	std::vector<std::string> decode(const cv::Mat& img, std::vector<cv::Rect>& bboxes, std::vector<cv::Rect>& points);

	//获取缩放列表
	std::vector<float> getScaleList(const int width, const int height);

	//缩放图像
	cv::Mat processImageScale(const cv::Mat &src, float scale);

	//目标检测器
	std::shared_ptr<YoloWrapper> detector_;

	//一维码检测器
	std::shared_ptr<BarcodeDetector> bardetector_;
};

CodeDetector::CodeDetector(const std::string & detector_config_path,
	const std::string & detector_model_path,
	const std::string & super_resolution_prototxt_path,
	const std::string & super_resolution_caffe_model_path) {
	p = cv::makePtr<CodeDetector::Impl>();
	if (!detector_config_path.empty() && !detector_model_path.empty()) {
		p->detector_ = std::make_shared<YoloWrapper>();
		p->detector_->init(detector_config_path, detector_model_path, cv::Size(960, 960));
	}
	else {
		p->detector_ = NULL;
	}
	p->bardetector_ = std::make_shared<BarcodeDetector>();
	//TODO:初始化超分辨率图像模型
}

std::vector<std::string> CodeDetector::detectAndDecode(cv::InputArray img, std::vector<cv::Rect> &points, std::vector<cv::Rect> &__points) {
	CV_Assert(!img.empty());

	if (img.cols() <= 20 || img.rows() <= 20) {
		return std::vector<std::string>();
	}
	logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__:开始图像格式转换");
	cv::Mat input;
	int incn = img.channels();
	if (incn == 4) {
		cv::cvtColor(img, input, cv::COLOR_BGRA2BGR);
	}
	else if (incn == 1) {
		cv::cvtColor(img, input, cv::COLOR_GRAY2BGR);//根据配置支持三通道图像
	}
	else {
		input = img.getMat();
	}
	logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__:图像格式转换完成");
	_mtx.lock();
	int x = 0;
	//识别
	logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__:开始识别二维码");
	std::vector<cv::Rect> bboxes = p->detect(input);
	for (auto&bbox : bboxes) {
		__points.push_back(bbox);
	}
	logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__:识别二维码结束");

	logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__:开始解码");
	//解码
	std::vector<std::string> results = p->decode(input, bboxes, points);
	logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__:解码完成");
	_mtx.unlock();
	return results;
}

std::vector<cv::Rect> CodeDetector::Impl::detect(const cv::Mat& img) {
	return detector_->forward(img);
}

std::vector<std::string> CodeDetector::Impl::decode(const cv::Mat& img, std::vector<cv::Rect>& bboxes, std::vector<cv::Rect>& points) {
	if (bboxes.size() == 0) {
		logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:未识别到二维码");
		return std::vector<std::string>();
	}
	std::vector<std::string> decode_results;
	//限定框,防止越界
	for (auto& bbox : bboxes) {
		if (bbox.height > 1000 || bbox.width > 1000) {
			logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:识别尺寸太大,判断不是二维码");
			continue;
		}
		float padding_w = 0.1f, padding_h = 0.1f;
		auto min_padding = 15;
		int padx = (int)cv::max(padding_w * bbox.width, static_cast<float>(min_padding));
		int pady = (int)cv::max(padding_h * bbox.height, static_cast<float>(min_padding));

		int crop_x_ = cv::max(bbox.tl().x - padx, 0);
		int crop_y_ = cv::max(bbox.tl().y - pady, 0);
		int end_x = cv::min(bbox.br().x + padx, img.cols - 1);
		int end_y = cv::min(bbox.br().y + pady, img.rows - 1);

		logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:裁剪图像");
		cv::Rect crop_roi(crop_x_, crop_y_, end_x - crop_x_ + 1, end_y - crop_y_ + 1);
		cv::Mat cropped_img = img(crop_roi);
		//转单通道
		cv::cvtColor(cropped_img, cropped_img, cv::COLOR_BGR2GRAY);
		logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:获取多尺度信息");
		auto scale_list = getScaleList(cropped_img.cols, cropped_img.rows);
		for (auto cur_scale : scale_list) {
			//缩放图像
			cv::Mat scaled_img =
				processImageScale(cropped_img, cur_scale);
			std::string result;
			//解码
			logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:开始调用外部dll解码");
			std::vector<cv::Point> _points;
			auto ret = decodeImage(scaled_img.clone(), result, false, 0, _points);
			logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:调用外部dll解码完成");
			if (ret != -1) {
				//恢复真实尺度
				logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:恢复真实尺度");
				for (auto& _point : _points) {
					_point.x = cvRound((float)_point.x / cur_scale);
					_point.x += crop_roi.tl().x;
					_point.y = cvRound((float)_point.y / cur_scale);
					_point.y += crop_roi.tl().y;
				}
				logger.t("__eyemDetectAndDecodeUseNN__detectAndDecode__decode__:解码成功");
				cv::Rect br = cv::boundingRect(_points);
				points.push_back(br);
				decode_results.push_back(result);
				break;
			}
		}
	}
	return decode_results;
}

std::vector<std::string> CodeDetector::detectAndDecodeBarcode(cv::Mat& img, cv::Mat &showMat, std::vector<cv::Point> &points) {

	CV_Assert(!img.empty());

	if (img.cols <= 40 || img.rows <= 40) {
		return std::vector<std::string>();
	}
	cv::Mat input;
	int incn = img.channels();
	if (incn == 4) {
		cv::cvtColor(img, input, cv::COLOR_BGRA2BGR);
	}
	else if (incn == 1) {
		cv::cvtColor(img, input, cv::COLOR_GRAY2BGR);//根据配置支持三通道图像
	}
	else {
		input = img;
	}

	//检测
	std::vector<cv::Point> _points;
	p->bardetector_->detect(input, _points);
	//转单通道
	cv::Mat input_gray;
	cv::cvtColor(input, input_gray, cv::COLOR_BGR2GRAY);
	//解码
	std::vector<std::string> results;
	if (!_points.empty()) {
		for (int n = 0; n < _points.size(); n += 4)
		{
			std::vector<cv::Point> __points(_points.begin() + n, _points.begin() + n + 4);
			//防止越界
			cv::Rect box = cv::boundingRect(__points)&cv::Rect(0, 0, input.cols, input.rows);
			//
			cv::Mat _input = input_gray(box);

			cv::Point _t = (__points[2] + __points[3]) / 2 - (__points[0] + __points[1]) / 2;

			double t = atan2(_t.y, _t.x)*180.0 / CV_PI;

			const int tckH = _input.rows, tckW = _input.cols;

			int tckdW = cvRound((double)tckH * abs(sin(t * CV_PI / 180.)) + (double)tckW * abs(cos(t * CV_PI / 180.)));

			int tckdH = cvRound(ceil((double)tckW * abs(sin(t * CV_PI / 180.)) + (double)tckH * abs(cos(t * CV_PI / 180.))));

			//创建矩阵
			cv::Mat matx23f(2, 3, CV_64F);
			matx23f = cv::getRotationMatrix2D(cv::Point2f((float)tckW / 2.0f - 0.5f, (float)tckH / 2.0f - 0.5f), t, 1.0);

			//由于旋转产生的偏移
			matx23f.ptr<double>(0)[2] += (float)(tckdW - tckW) / 2.0;
			matx23f.ptr<double>(1)[2] += (float)(tckdH - tckH) / 2.0;

			//输出矩阵
			float matx[6];
			matx[0] = (float)matx23f.ptr<double>(0)[0]; matx[1] = (float)matx23f.ptr<double>(0)[1]; matx[2] = (float)matx23f.ptr<double>(0)[2];
			matx[3] = (float)matx23f.ptr<double>(1)[0]; matx[4] = (float)matx23f.ptr<double>(1)[1]; matx[5] = (float)matx23f.ptr<double>(1)[2];

			//仿射变换
			cv::Mat __input;
			cv::warpAffine(_input, __input, matx23f, cv::Size(tckdW, tckdH), cv::INTER_CUBIC, cv::BORDER_REPLICATE);

			//计算变换后的点
			for (auto&__point : __points) {
				cv::Point pt(__point.x - box.x, __point.y - box.y);
				__point.x = cvRound((float)pt.x*matx[0] + (float)pt.y*matx[1] + matx[2]);
				__point.y = cvRound((float)pt.x*matx[3] + (float)pt.y*matx[4] + matx[5]);
			}
			auto scale_list = { 1.0f,2.0f };

			for (auto cur_scale : scale_list) {
				cv::Mat scaled_img =
					p->processImageScale(__input(cv::boundingRect(__points)&cv::Rect(0, 0, __input.cols, input.rows)), cur_scale);
				std::string result;
				int iRet = decodeImage(scaled_img(cv::Range(0, cv::min(21, scaled_img.rows)), cv::Range::all()), result, true);
				if (iRet != -1) {
					//画图
					cv::Point pt = cv::Point(box.tl() + box.br()) / 2;
					cv::putText(input, result, pt, cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);
					//输出
					points.push_back(pt);
					results.push_back(result);
					break;
				}
			}
		}
	}
	p->bardetector_->drawBarcode(showMat, _points);
	return results;
}

std::vector<float>  CodeDetector::Impl::getScaleList(const int width, const int height) {
	if (width < 320 || height < 320) return{ 1.0, 2.0, 0.5 };
	if (width < 640 && height < 640) return{ 1.0, 0.5 };
	return{ 0.5, 1.0 };
}

cv::Mat CodeDetector::Impl::processImageScale(const cv::Mat &src, float scale) {
	cv::Mat sr = src;
	if (scale == 1.0) {
		return sr;
	}
	cv::resize(src, sr, cv::Size(), scale, scale, cv::INTER_CUBIC);
	return sr;
}