a
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
#include "ColorReader.h"
|
||||
#include "GlobalParams.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
ColorReader::ColorReader() {
|
||||
}
|
||||
|
||||
ColorReader::~ColorReader() {
|
||||
}
|
||||
|
||||
std::vector<ColorResult> ColorReader::readColors(
|
||||
const cv::Mat& image,
|
||||
const std::vector<cv::Point2f>& centers,
|
||||
bool smallLocatorOnLeft,
|
||||
bool applyWB) {
|
||||
|
||||
std::vector<ColorResult> results;
|
||||
|
||||
if (image.empty() || centers.empty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
// 复制图像用于颜色读取(避免绘图干扰)
|
||||
cv::Mat colorImage = image.clone();
|
||||
|
||||
// 白平衡处理(可选)
|
||||
if (applyWB) {
|
||||
applyWhiteBalance(colorImage);
|
||||
}
|
||||
|
||||
// 确定采样半径
|
||||
int radius = GlobalParams::DEFAULT_SAMPLE_RADIUS;
|
||||
|
||||
// 采样每个色块的颜色
|
||||
for (size_t i = 0; i < centers.size(); ++i) {
|
||||
ColorResult result;
|
||||
result.id = static_cast<int>(i);
|
||||
|
||||
// 采样颜色
|
||||
cv::Scalar bgr = sampleColor(colorImage, centers[i], radius);
|
||||
|
||||
// 转换颜色空间
|
||||
convertColorSpaces(bgr, result.h, result.s, result.v,
|
||||
result.l, result.a, result.b);
|
||||
|
||||
result.r = bgr[2];
|
||||
result.g = bgr[1];
|
||||
result.b_val = bgr[0];
|
||||
|
||||
results.push_back(result);
|
||||
}
|
||||
|
||||
// 调整顺序(确保从靠近小定位块的色块开始)
|
||||
results = adjustOrder(results, smallLocatorOnLeft);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void ColorReader::applyWhiteBalance(cv::Mat& image) {
|
||||
if (image.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保是BGR格式
|
||||
cv::Mat bgrImage;
|
||||
if (image.channels() == 4) {
|
||||
cv::cvtColor(image, bgrImage, cv::COLOR_BGRA2BGR);
|
||||
} else if (image.channels() == 1) {
|
||||
cv::cvtColor(image, bgrImage, cv::COLOR_GRAY2BGR);
|
||||
} else {
|
||||
bgrImage = image;
|
||||
}
|
||||
|
||||
// 计算各通道均值
|
||||
cv::Scalar sumChannels = cv::sum(bgrImage);
|
||||
int totalPixels = bgrImage.rows * bgrImage.cols;
|
||||
|
||||
if (totalPixels == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
double avgB = sumChannels[0] / totalPixels;
|
||||
double avgG = sumChannels[1] / totalPixels;
|
||||
double avgR = sumChannels[2] / totalPixels;
|
||||
|
||||
// 避免除零
|
||||
if (avgB < 1) avgB = 1;
|
||||
if (avgG < 1) avgG = 1;
|
||||
if (avgR < 1) avgR = 1;
|
||||
|
||||
// 计算目标均值(三通道平均)
|
||||
double targetAvg = (avgB + avgG + avgR) / 3.0;
|
||||
|
||||
if (targetAvg < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 各通道缩放校正
|
||||
std::vector<cv::Mat> channels(3);
|
||||
cv::split(bgrImage, channels);
|
||||
|
||||
channels[0] = channels[0] * (targetAvg / avgB);
|
||||
channels[1] = channels[1] * (targetAvg / avgG);
|
||||
channels[2] = channels[2] * (targetAvg / avgR);
|
||||
|
||||
cv::merge(channels, bgrImage);
|
||||
bgrImage.convertTo(image, CV_8UC3);
|
||||
}
|
||||
|
||||
cv::Scalar ColorReader::sampleColor(const cv::Mat& image, const cv::Point2f& center, int radius) {
|
||||
int cx = static_cast<int>(center.x);
|
||||
int cy = static_cast<int>(center.y);
|
||||
|
||||
// 确保采样区域在图像内
|
||||
int x1 = std::max(0, cx - radius);
|
||||
int y1 = std::max(0, cy - radius);
|
||||
int x2 = std::min(image.cols - 1, cx + radius);
|
||||
int y2 = std::min(image.rows - 1, cy + radius);
|
||||
|
||||
if (x2 <= x1 || y2 <= y1) {
|
||||
return cv::Scalar(0, 0, 0);
|
||||
}
|
||||
|
||||
// 创建采样掩码(圆形)
|
||||
cv::Mat roi = image(cv::Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1));
|
||||
cv::Mat mask = cv::Mat::zeros(roi.size(), CV_8U);
|
||||
cv::Point maskCenter(cx - x1, cy - y1);
|
||||
cv::circle(mask, maskCenter, radius, cv::Scalar(255), -1);
|
||||
|
||||
// 计算掩码区域的平均颜色
|
||||
cv::Scalar avgColor = cv::mean(roi, mask);
|
||||
|
||||
return avgColor;
|
||||
}
|
||||
|
||||
void ColorReader::convertColorSpaces(const cv::Scalar& bgr,
|
||||
double& h, double& s, double& v,
|
||||
double& l, double& a, double& b) {
|
||||
// 创建1x1像素的图像进行颜色转换
|
||||
cv::Mat bgrMat(1, 1, CV_8UC3, cv::Scalar(bgr[0], bgr[1], bgr[2]));
|
||||
|
||||
// 转换到HSV
|
||||
cv::Mat hsvMat;
|
||||
cv::cvtColor(bgrMat, hsvMat, cv::COLOR_BGR2HSV);
|
||||
cv::Vec3b hsv = hsvMat.at<cv::Vec3b>(0, 0);
|
||||
h = hsv[0] * 2.0; // OpenCV的H范围是0-180,转换为0-360
|
||||
s = hsv[1];
|
||||
v = hsv[2];
|
||||
|
||||
// 转换到LAB
|
||||
cv::Mat labMat;
|
||||
cv::cvtColor(bgrMat, labMat, cv::COLOR_BGR2Lab);
|
||||
cv::Vec3b lab = labMat.at<cv::Vec3b>(0, 0);
|
||||
l = lab[0];
|
||||
a = static_cast<double>(lab[1]) - 128.0; // a范围从0-255转为-128到127
|
||||
b = static_cast<double>(lab[2]) - 128.0; // b范围从0-255转为-128到127
|
||||
}
|
||||
|
||||
std::vector<ColorResult> ColorReader::adjustOrder(
|
||||
std::vector<ColorResult>& colors,
|
||||
bool smallLocatorOnLeft) {
|
||||
|
||||
// 如果小定位块在左边,颜色顺序已经是正确的
|
||||
// 如果小定位块在右边,需要反转顺序
|
||||
|
||||
if (!smallLocatorOnLeft && colors.size() > 1) {
|
||||
std::reverse(colors.begin(), colors.end());
|
||||
}
|
||||
|
||||
// 重新编号
|
||||
for (size_t i = 0; i < colors.size(); ++i) {
|
||||
colors[i].id = static_cast<int>(i);
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
|
||||
class ColorReader {
|
||||
public:
|
||||
ColorReader();
|
||||
~ColorReader();
|
||||
|
||||
// 执行颜色读取
|
||||
std::vector<ColorResult> readColors(
|
||||
const cv::Mat& image,
|
||||
const std::vector<cv::Point2f>& centers,
|
||||
bool smallLocatorOnLeft,
|
||||
bool applyWB = true // 是否应用白平衡
|
||||
);
|
||||
|
||||
private:
|
||||
// 白平衡处理
|
||||
void applyWhiteBalance(cv::Mat& image);
|
||||
|
||||
// 采样颜色
|
||||
cv::Scalar sampleColor(const cv::Mat& image, const cv::Point2f& center, int radius);
|
||||
|
||||
// 颜色空间转换
|
||||
void convertColorSpaces(const cv::Scalar& bgr,
|
||||
double& h, double& s, double& v,
|
||||
double& l, double& a, double& b);
|
||||
|
||||
// 调整顺序(始终从靠近小定位块开始)
|
||||
std::vector<ColorResult> adjustOrder(
|
||||
std::vector<ColorResult>& colors,
|
||||
bool smallLocatorOnLeft
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
struct GlobalParams {
|
||||
// 默认采样半径
|
||||
static constexpr int DEFAULT_SAMPLE_RADIUS = 5;
|
||||
|
||||
// 轮廓过滤
|
||||
static constexpr double MIN_CONTOUR_AREA = 100.0;
|
||||
static constexpr double MAX_AREA_RATIO = 0.3;
|
||||
static constexpr double MIN_RECTANGULARITY = 0.5;
|
||||
|
||||
// 定位块宽高比
|
||||
static constexpr double BIG_LOCATOR_RATIO_SHORT = 2.0; // 10mm: 10/5
|
||||
static constexpr double BIG_LOCATOR_RATIO_LONG = 3.0; // 15mm: 15/5
|
||||
static constexpr double SMALL_LOCATOR_RATIO = 1.667; // 5mm/3mm
|
||||
|
||||
// 面积范围
|
||||
static constexpr double MIN_BIG_LOCATOR_AREA = 200.0;
|
||||
static constexpr double MAX_BIG_LOCATOR_AREA = 8000.0;
|
||||
static constexpr double MIN_SMALL_LOCATOR_AREA = 50.0;
|
||||
static constexpr double MAX_SMALL_LOCATOR_AREA = 2000.0;
|
||||
|
||||
// 颜色匹配
|
||||
static constexpr double COLOR_DISTANCE_THRESHOLD = 60.0;
|
||||
|
||||
// 面积比
|
||||
static constexpr double AREA_RATIO_SHORT = 3.33; // 10mm大/3mm小
|
||||
static constexpr double AREA_RATIO_LONG = 5.0; // 15mm大/3mm小
|
||||
static constexpr double AREA_ERROR_RATIO = 0.4; // 40%
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
#include "PaperConfig.h"
|
||||
#include "GlobalParams.h"
|
||||
#include <cmath>
|
||||
|
||||
PaperConfigManager& PaperConfigManager::getInstance() {
|
||||
static PaperConfigManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
PaperConfigManager::PaperConfigManager() {
|
||||
initConfigs();
|
||||
}
|
||||
|
||||
void PaperConfigManager::initConfigs() {
|
||||
// PST_14_S: 14联, 10mm大定位块
|
||||
PaperConfig config14s;
|
||||
config14s.type = PaperSpatialType::PST_14_S;
|
||||
config14s.blockItems = BlockItems::BI_14;
|
||||
config14s.locatorType = BigLocatorType::BLT_SHORT;
|
||||
config14s.itemsNum = 14;
|
||||
config14s.slicesNum = 50;
|
||||
config14s.firstPos = 2.25;
|
||||
config14s.step = 3.5;
|
||||
config14s.distTimes = 21.3;
|
||||
config14s.whBigRatio = GlobalParams::BIG_LOCATOR_RATIO_SHORT;
|
||||
configs_[PaperSpatialType::PST_14_S] = config14s;
|
||||
|
||||
// PST_14_L: 14联, 15mm大定位块
|
||||
PaperConfig config14l;
|
||||
config14l.type = PaperSpatialType::PST_14_L;
|
||||
config14l.blockItems = BlockItems::BI_14;
|
||||
config14l.locatorType = BigLocatorType::BLT_LONG;
|
||||
config14l.itemsNum = 14;
|
||||
config14l.slicesNum = 50;
|
||||
config14l.firstPos = 2.25;
|
||||
config14l.step = 3.5;
|
||||
config14l.distTimes = 21.8;
|
||||
config14l.whBigRatio = GlobalParams::BIG_LOCATOR_RATIO_LONG;
|
||||
configs_[PaperSpatialType::PST_14_L] = config14l;
|
||||
|
||||
// PST_11_S: 11联, 10mm大定位块
|
||||
PaperConfig config11s;
|
||||
config11s.type = PaperSpatialType::PST_11_S;
|
||||
config11s.blockItems = BlockItems::BI_11;
|
||||
config11s.locatorType = BigLocatorType::BLT_SHORT;
|
||||
config11s.itemsNum = 11;
|
||||
config11s.slicesNum = 39;
|
||||
config11s.firstPos = 2.25;
|
||||
config11s.step = 3.5;
|
||||
config11s.distTimes = 17.6;
|
||||
config11s.whBigRatio = GlobalParams::BIG_LOCATOR_RATIO_SHORT;
|
||||
configs_[PaperSpatialType::PST_11_S] = config11s;
|
||||
|
||||
// PST_11_L: 11联, 15mm大定位块
|
||||
PaperConfig config11l;
|
||||
config11l.type = PaperSpatialType::PST_11_L;
|
||||
config11l.blockItems = BlockItems::BI_11;
|
||||
config11l.locatorType = BigLocatorType::BLT_LONG;
|
||||
config11l.itemsNum = 11;
|
||||
config11l.slicesNum = 39;
|
||||
config11l.firstPos = 2.25;
|
||||
config11l.step = 3.5;
|
||||
config11l.distTimes = 18.1;
|
||||
config11l.whBigRatio = GlobalParams::BIG_LOCATOR_RATIO_LONG;
|
||||
configs_[PaperSpatialType::PST_11_L] = config11l;
|
||||
|
||||
// PST_8_S: 8联, 10mm大定位块
|
||||
PaperConfig config8s;
|
||||
config8s.type = PaperSpatialType::PST_8_S;
|
||||
config8s.blockItems = BlockItems::BI_8;
|
||||
config8s.locatorType = BigLocatorType::BLT_SHORT;
|
||||
config8s.itemsNum = 8;
|
||||
config8s.slicesNum = 29;
|
||||
config8s.firstPos = 2.25;
|
||||
config8s.step = 3.5;
|
||||
config8s.distTimes = 12.9;
|
||||
config8s.whBigRatio = GlobalParams::BIG_LOCATOR_RATIO_SHORT;
|
||||
configs_[PaperSpatialType::PST_8_S] = config8s;
|
||||
|
||||
// PST_8_L: 8联, 15mm大定位块
|
||||
PaperConfig config8l;
|
||||
config8l.type = PaperSpatialType::PST_8_L;
|
||||
config8l.blockItems = BlockItems::BI_8;
|
||||
config8l.locatorType = BigLocatorType::BLT_LONG;
|
||||
config8l.itemsNum = 8;
|
||||
config8l.slicesNum = 29;
|
||||
config8l.firstPos = 2.25;
|
||||
config8l.step = 3.5;
|
||||
config8l.distTimes = 13.4;
|
||||
config8l.whBigRatio = GlobalParams::BIG_LOCATOR_RATIO_LONG;
|
||||
configs_[PaperSpatialType::PST_8_L] = config8l;
|
||||
}
|
||||
|
||||
const PaperConfig* PaperConfigManager::getConfig(PaperSpatialType type) const {
|
||||
auto it = configs_.find(type);
|
||||
if (it != configs_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PaperSpatialType PaperConfigManager::matchConfig(double distRatio, double areaRatio,
|
||||
BigLocatorType locatorType) const {
|
||||
// 参照老代码 ImgLocateDryChemistry::verifyPaperConfig 的逻辑
|
||||
// 先根据面积比判断大/小规格,再根据距离比判断具体联数
|
||||
|
||||
// 面积比范围定义(参照老代码)
|
||||
double Min_AreaRadio_S = 2.6;
|
||||
double Max_AreaRadio_S = 4;
|
||||
double Min_AreaRadio_L = 4.2;
|
||||
double Max_AreaRadio_L = 6.0;
|
||||
|
||||
// 距离比范围定义(基于实际数据统计)
|
||||
// S型(10mm大定位块)
|
||||
// 14S典型值: 20.5-24.0(考虑不同拍摄距离), 11S典型值: 16.5-18.5
|
||||
// 临界区域 [18.5, 19.0] 作为空白缓冲,避免误判
|
||||
double Min_DistRadio_14S = 19.0, Max_DistRadio_14S = 26.5; // 14S: 19.0-26.5(扩大上限覆盖远距离拍摄)
|
||||
double Min_DistRadio_11S = 15.5, Max_DistRadio_11S = 18.5; // 11S: 15.5-18.5(与14S留0.5缓冲)
|
||||
double Min_DistRadio_8S = 11.6, Max_DistRadio_8S = 14.1;
|
||||
// L型(15mm大定位块)
|
||||
double Min_DistRadio_14L = 19.0, Max_DistRadio_14L = 26.98; // 同步调整
|
||||
double Min_DistRadio_11L = 15.5, Max_DistRadio_11L = 18.5; // 同步调整
|
||||
double Min_DistRadio_8L = 12.0, Max_DistRadio_8L = 14.5;
|
||||
|
||||
// 检查面积比是否在有效范围内
|
||||
if (areaRatio < Min_AreaRadio_S || areaRatio > Max_AreaRadio_L) {
|
||||
return PaperSpatialType::PST_UNKNOWN;
|
||||
}
|
||||
|
||||
// 空白区间 [4.0, 4.2] 不做判断,避免误判
|
||||
if (areaRatio > Max_AreaRadio_S && areaRatio < Min_AreaRadio_L) {
|
||||
return PaperSpatialType::PST_UNKNOWN;
|
||||
}
|
||||
|
||||
// 根据面积比判断大/小规格
|
||||
bool isSmallType = (areaRatio <= Max_AreaRadio_S);
|
||||
|
||||
if (isSmallType) {
|
||||
// S型(10mm大定位块)
|
||||
if (distRatio > Min_DistRadio_14S && distRatio < Max_DistRadio_14S) {
|
||||
return PaperSpatialType::PST_14_S;
|
||||
} else if (distRatio > Min_DistRadio_11S && distRatio < Max_DistRadio_11S) {
|
||||
return PaperSpatialType::PST_11_S;
|
||||
} else if (distRatio > Min_DistRadio_8S && distRatio < Max_DistRadio_8S) {
|
||||
return PaperSpatialType::PST_8_S;
|
||||
}
|
||||
} else {
|
||||
// L型(15mm大定位块)
|
||||
if (distRatio > Min_DistRadio_14L && distRatio < Max_DistRadio_14L) {
|
||||
return PaperSpatialType::PST_14_L;
|
||||
} else if (distRatio > Min_DistRadio_11L && distRatio < Max_DistRadio_11L) {
|
||||
return PaperSpatialType::PST_11_L;
|
||||
} else if (distRatio > Min_DistRadio_8L && distRatio < Max_DistRadio_8L) {
|
||||
return PaperSpatialType::PST_8_L;
|
||||
}
|
||||
}
|
||||
|
||||
return PaperSpatialType::PST_UNKNOWN;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include <map>
|
||||
|
||||
// 单个试纸规格配置
|
||||
struct PaperConfig {
|
||||
PaperSpatialType type; // 试纸类型
|
||||
BlockItems blockItems; // 联数
|
||||
BigLocatorType locatorType; // 大定位块类型
|
||||
int itemsNum; // 反应色块数量
|
||||
int slicesNum; // 最小分割单位数量
|
||||
double firstPos; // 第一个色块位置倍数
|
||||
double step; // 步长倍数
|
||||
double distTimes; // 定位块中心距离/试纸高度
|
||||
double whBigRatio; // 大定位块宽高比
|
||||
|
||||
PaperConfig() : type(PaperSpatialType::PST_UNKNOWN),
|
||||
blockItems(BlockItems::BI_14),
|
||||
locatorType(BigLocatorType::BLT_SHORT),
|
||||
itemsNum(0), slicesNum(0), firstPos(0),
|
||||
step(0), distTimes(0), whBigRatio(0) {}
|
||||
};
|
||||
|
||||
// 试纸配置管理器
|
||||
class PaperConfigManager {
|
||||
public:
|
||||
static PaperConfigManager& getInstance();
|
||||
|
||||
const PaperConfig* getConfig(PaperSpatialType type) const;
|
||||
PaperSpatialType matchConfig(double distRatio, double areaRatio,
|
||||
BigLocatorType locatorType) const;
|
||||
|
||||
private:
|
||||
PaperConfigManager();
|
||||
PaperConfigManager(const PaperConfigManager&) = delete;
|
||||
PaperConfigManager& operator=(const PaperConfigManager&) = delete;
|
||||
|
||||
void initConfigs();
|
||||
|
||||
std::map<PaperSpatialType, PaperConfig> configs_;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "ColorReader.h"
|
||||
#include "PaperConfig.h"
|
||||
#include "GlobalParams.h"
|
||||
|
||||
class PaperRecognizer {
|
||||
public:
|
||||
PaperRecognizer();
|
||||
~PaperRecognizer();
|
||||
|
||||
// 识别试纸
|
||||
RecognitionResult recognize(const cv::Mat& image);
|
||||
|
||||
// 获取裁剪后的试纸图
|
||||
cv::Mat getPaperImg() const;
|
||||
|
||||
// 读取所有色块颜色
|
||||
std::vector<ColorResult> readAllColorBlocks(const cv::Mat& bgrImage);
|
||||
|
||||
private:
|
||||
// 定位块配对
|
||||
struct LocatorPair {
|
||||
bool isValid;
|
||||
LocatorInfo* big;
|
||||
LocatorInfo* small;
|
||||
LocatorPair() : isValid(false), big(nullptr), small(nullptr) {}
|
||||
};
|
||||
|
||||
void preprocessImage(const cv::Mat& image);
|
||||
bool detectLocators();
|
||||
void findCandidateContours(std::vector<std::vector<cv::Point>>& contours);
|
||||
bool filterLocatorCandidates(const std::vector<std::vector<cv::Point>>& contours);
|
||||
std::vector<std::vector<LocatorInfo*>> groupCandidatesByY(std::vector<LocatorInfo>& candidates);
|
||||
LocatorPair findValidLocatorPairInGroup(std::vector<LocatorInfo*>& group);
|
||||
bool validateLocatorPair();
|
||||
double calculateAspectRatio(const cv::RotatedRect& rect);
|
||||
bool isPotentialLocator(const cv::RotatedRect& rect, double area, double& aspectRatio);
|
||||
void sortLocatorsByPosition();
|
||||
BigLocatorType determineBigLocatorType(double areaRatio);
|
||||
bool matchPaperSpec();
|
||||
void calculateColorBlockCenters();
|
||||
BlurLevel assessLocatorBlur(const cv::Mat& gray, const cv::RotatedRect& locator, double& score);
|
||||
void drawAnnotations();
|
||||
void savePaperImg();
|
||||
LocatorColor determineLocatorColor(const cv::Rect& roi);
|
||||
LocatorColor determineLocatorColorByCenter(const cv::RotatedRect& rotatedRect);
|
||||
cv::Scalar getCenterRegionColor(const cv::RotatedRect& rotatedRect);
|
||||
cv::Scalar getAverageColor(const cv::Rect& roi);
|
||||
double measureSaturation(const cv::Mat& hsvRoi);
|
||||
double calculateColorDistance(const cv::Scalar& c1, const cv::Scalar& c2);
|
||||
|
||||
cv::Mat originalImage_; // 原图
|
||||
cv::Mat processImage_; // 处理用图像
|
||||
cv::Mat hsvImage_; // HSV图像
|
||||
cv::Mat grayImage_; // 灰度图像
|
||||
cv::Mat paperImg_; // 裁剪后的试纸图片(RGBA格式)
|
||||
|
||||
bool hasLocator1_;
|
||||
bool hasLocator2_;
|
||||
LocatorInfo locator1_;
|
||||
LocatorInfo locator2_;
|
||||
LocatorInfo* bigLocator_;
|
||||
LocatorInfo* smallLocator_;
|
||||
|
||||
std::vector<cv::Point2f> centers_;
|
||||
RecognitionResult result_;
|
||||
const PaperConfig* config_;
|
||||
ColorReader colorReader_;
|
||||
|
||||
size_t candidateCount_;
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// 试纸联数
|
||||
enum class BlockItems {
|
||||
BI_14 = 14,
|
||||
BI_11 = 11,
|
||||
BI_8 = 8
|
||||
};
|
||||
|
||||
// 大定位块类型
|
||||
enum class BigLocatorType {
|
||||
BLT_SHORT = 0, // 10mm
|
||||
BLT_LONG = 1 // 15mm
|
||||
};
|
||||
|
||||
// 定位块颜色分类
|
||||
enum class LocatorColor {
|
||||
UNKNOWN = 0,
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3
|
||||
};
|
||||
|
||||
// 试纸空间类型
|
||||
enum class PaperSpatialType {
|
||||
PST_UNKNOWN = 0,
|
||||
PST_14_S = 1, // 14联, 10mm大定位块
|
||||
PST_14_L = 2, // 14联, 15mm大定位块
|
||||
PST_11_S = 3, // 11联, 10mm大定位块
|
||||
PST_11_L = 4, // 11联, 15mm大定位块
|
||||
PST_8_S = 5, // 8联, 10mm大定位块
|
||||
PST_8_L = 6 // 8联, 15mm大定位块
|
||||
};
|
||||
|
||||
// 图像清晰度等级
|
||||
enum class BlurLevel {
|
||||
BLUR_NONE = 0, // 清晰
|
||||
BLUR_SLIGHT = 1, // 轻微模糊
|
||||
BLUR_SEVERE = 2 // 严重模糊
|
||||
};
|
||||
|
||||
// 定位块信息
|
||||
struct LocatorInfo {
|
||||
cv::RotatedRect rotatedRect; // 旋转矩形
|
||||
cv::Point2f center; // 中心点
|
||||
double area; // 面积
|
||||
double width; // 宽度(长边)
|
||||
double height; // 高度(短边)
|
||||
LocatorColor color; // 颜色
|
||||
bool isBig; // 是否大定位块
|
||||
cv::Scalar avgColor; // 平均RGB颜色
|
||||
|
||||
LocatorInfo() : area(0), width(0), height(0),
|
||||
color(LocatorColor::UNKNOWN), isBig(false) {}
|
||||
};
|
||||
|
||||
// 色块颜色结果
|
||||
struct ColorResult {
|
||||
int id; // 编号(从靠近小定位块开始)
|
||||
double h, s, v; // HSV值
|
||||
double l, a, b; // LAB值
|
||||
double r, g, b_val; // RGB值
|
||||
|
||||
ColorResult() : id(0), h(0), s(0), v(0), l(0), a(0), b(0),
|
||||
r(0), g(0), b_val(0) {}
|
||||
};
|
||||
|
||||
// 识别结果
|
||||
struct RecognitionResult {
|
||||
bool success; // 是否成功
|
||||
int errorCode; // 错误码
|
||||
std::string errorMessage; // 错误信息
|
||||
PaperSpatialType paperType; // 试纸类型
|
||||
LocatorColor locatorColor; // 定位块颜色
|
||||
int itemCount; // 色块数量
|
||||
std::vector<cv::Point2f> colorBlockCenters; // 色块中心点
|
||||
std::vector<ColorResult> colors; // 颜色结果
|
||||
cv::Mat annotatedImage; // 标注后的图片
|
||||
bool smallLocatorOnLeft; // 小定位块是否在左边
|
||||
BlurLevel blurLevel; // 图像清晰度等级
|
||||
double blurScore; // 清晰度分数(方差值)
|
||||
|
||||
RecognitionResult() : success(false), errorCode(-1),
|
||||
paperType(PaperSpatialType::PST_UNKNOWN),
|
||||
locatorColor(LocatorColor::UNKNOWN),
|
||||
itemCount(0), smallLocatorOnLeft(true),
|
||||
blurLevel(BlurLevel::BLUR_NONE), blurScore(0) {}
|
||||
};
|
||||
@@ -0,0 +1,236 @@
|
||||
#include <emscripten.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#include "PaperRecognizer.h"
|
||||
#include "ColorReader.h"
|
||||
#include "PaperConfig.h"
|
||||
|
||||
// 存储结果供异步访问
|
||||
static RecognitionResult s_result;
|
||||
static std::string s_jsonResult;
|
||||
static cv::Mat s_annotatedImage;
|
||||
|
||||
// JSON 转义帮助函数
|
||||
static std::string escapeJson(const std::string& s) {
|
||||
std::string out;
|
||||
out.reserve(s.size());
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '\"': out += "\\\""; break;
|
||||
case '\\': out += "\\\\"; break;
|
||||
case '\b': out += "\\b"; break;
|
||||
case '\f': out += "\\f"; break;
|
||||
case '\n': out += "\\n"; break;
|
||||
case '\r': out += "\\r"; break;
|
||||
case '\t': out += "\\t"; break;
|
||||
default:
|
||||
if (static_cast<unsigned char>(c) < 0x20) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "\\u%04x", c);
|
||||
out += buf;
|
||||
} else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// 颜色结果转 JSON
|
||||
static std::string colorResultToJson(const ColorResult& c) {
|
||||
std::ostringstream json;
|
||||
json << "{";
|
||||
json << "\"id\":" << c.id << ",";
|
||||
json << "\"h\":" << c.h << ",\"s\":" << c.s << ",\"v\":" << c.v << ",";
|
||||
json << "\"l\":" << c.l << ",\"a\":" << c.a << ",\"b\":" << static_cast<double>(c.b_val) << ",";
|
||||
json << "\"r\":" << c.r << ",\"g\":" << c.g << ",\"b_val\":" << c.b_val;
|
||||
json << "}";
|
||||
return json.str();
|
||||
}
|
||||
|
||||
// 定位块类型转字符串
|
||||
static const char* paperTypeString(PaperSpatialType type) {
|
||||
switch (type) {
|
||||
case PaperSpatialType::PST_14_S: return "14_S";
|
||||
case PaperSpatialType::PST_14_L: return "14_L";
|
||||
case PaperSpatialType::PST_11_S: return "11_S";
|
||||
case PaperSpatialType::PST_11_L: return "11_L";
|
||||
case PaperSpatialType::PST_8_S: return "8_S";
|
||||
case PaperSpatialType::PST_8_L: return "8_L";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
* 从原始 RGBA 像素数据识别试纸
|
||||
* @param data RGBA 像素数据指针
|
||||
* @param width 图像宽度
|
||||
* @param height 图像高度
|
||||
* @return 0=成功, -1=失败
|
||||
*/
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int recognize(unsigned char* data, int width, int height) {
|
||||
// 重置结果
|
||||
s_result = RecognitionResult();
|
||||
s_jsonResult.clear();
|
||||
s_annotatedImage.release();
|
||||
|
||||
if (!data || width <= 0 || height <= 0) {
|
||||
s_result.errorCode = -1;
|
||||
s_result.errorMessage = "Invalid input parameters";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 创建 OpenCV Mat
|
||||
cv::Mat rgba(height, width, CV_8UC4, data);
|
||||
if (rgba.empty()) {
|
||||
s_result.errorCode = -2;
|
||||
s_result.errorMessage = "Failed to create Mat from input data";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// RGBA → BGRA
|
||||
cv::Mat bgra;
|
||||
cv::cvtColor(rgba, bgra, cv::COLOR_RGBA2BGRA);
|
||||
|
||||
// 识别
|
||||
PaperRecognizer recognizer;
|
||||
s_result = recognizer.recognize(bgra);
|
||||
|
||||
if (s_result.success) {
|
||||
// 读取颜色
|
||||
ColorReader reader;
|
||||
cv::Mat bgrImage;
|
||||
cv::cvtColor(bgra, bgrImage, cv::COLOR_BGRA2BGR);
|
||||
s_result.colors = reader.readColors(bgrImage, s_result.colorBlockCenters,
|
||||
s_result.smallLocatorOnLeft, true);
|
||||
|
||||
// 重新标注(包括颜色结果)
|
||||
if (!s_result.colors.empty()) {
|
||||
// 在标注图上添加颜色数值文字
|
||||
for (size_t i = 0; i < s_result.colors.size() && i < s_result.colorBlockCenters.size(); ++i) {
|
||||
cv::Point2f center = s_result.colorBlockCenters[i];
|
||||
char text[64];
|
||||
snprintf(text, sizeof(text), "#%d R:%.0f G:%.0f B:%.0f",
|
||||
s_result.colors[i].id,
|
||||
s_result.colors[i].r,
|
||||
s_result.colors[i].g,
|
||||
s_result.colors[i].b_val);
|
||||
|
||||
cv::putText(s_result.annotatedImage, text,
|
||||
cv::Point(static_cast<int>(center.x) - 30,
|
||||
static_cast<int>(center.y) + 20),
|
||||
cv::FONT_HERSHEY_SIMPLEX, 0.3,
|
||||
cv::Scalar(255, 255, 255), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存标注图
|
||||
if (!s_result.annotatedImage.empty()) {
|
||||
s_annotatedImage = s_result.annotatedImage.clone();
|
||||
} else {
|
||||
s_annotatedImage = bgra.clone();
|
||||
}
|
||||
|
||||
return s_result.success ? 0 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取识别结果的 JSON 字符串
|
||||
* 调用者应当在使用后调用 freeResult()
|
||||
*/
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* getResultJson() {
|
||||
std::ostringstream json;
|
||||
json << "{";
|
||||
json << "\"success\":" << (s_result.success ? "true" : "false") << ",";
|
||||
json << "\"errorCode\":" << s_result.errorCode << ",";
|
||||
json << "\"errorMessage\":\"" << escapeJson(s_result.errorMessage) << "\",";
|
||||
json << "\"paperType\":\"" << paperTypeString(s_result.paperType) << "\",";
|
||||
json << "\"locatorColor\":" << static_cast<int>(s_result.locatorColor) << ",";
|
||||
json << "\"itemCount\":" << s_result.itemCount << ",";
|
||||
json << "\"smallLocatorOnLeft\":" << (s_result.smallLocatorOnLeft ? "true" : "false") << ",";
|
||||
json << "\"blurLevel\":" << static_cast<int>(s_result.blurLevel) << ",";
|
||||
json << "\"blurScore\":" << s_result.blurScore << ",";
|
||||
|
||||
// 色块中心点
|
||||
json << "\"colorBlockCenters\":[";
|
||||
for (size_t i = 0; i < s_result.colorBlockCenters.size(); ++i) {
|
||||
if (i > 0) json << ",";
|
||||
json << "{\"x\":" << s_result.colorBlockCenters[i].x
|
||||
<< ",\"y\":" << s_result.colorBlockCenters[i].y << "}";
|
||||
}
|
||||
json << "],";
|
||||
|
||||
// 颜色结果
|
||||
json << "\"colors\":[";
|
||||
for (size_t i = 0; i < s_result.colors.size(); ++i) {
|
||||
if (i > 0) json << ",";
|
||||
json << colorResultToJson(s_result.colors[i]);
|
||||
}
|
||||
json << "]";
|
||||
|
||||
json << "}";
|
||||
|
||||
s_jsonResult = json.str();
|
||||
return s_jsonResult.c_str();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标注图宽度
|
||||
*/
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getAnnotatedWidth() {
|
||||
return s_annotatedImage.cols;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标注图高度
|
||||
*/
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getAnnotatedHeight() {
|
||||
return s_annotatedImage.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标注图 BGRA 像素数据指针
|
||||
* 数据布局:BGRA BGRA BGRA ... 每像素4字节
|
||||
* 总大小 = width * height * 4
|
||||
*/
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
unsigned char* getAnnotatedPixels() {
|
||||
if (s_annotatedImage.empty()) return nullptr;
|
||||
|
||||
// 确保是 BGRA 格式
|
||||
if (s_annotatedImage.channels() == 3) {
|
||||
cv::Mat bgra;
|
||||
cv::cvtColor(s_annotatedImage, bgra, cv::COLOR_BGR2BGRA);
|
||||
s_annotatedImage = bgra;
|
||||
} else if (s_annotatedImage.channels() == 1) {
|
||||
cv::Mat bgra;
|
||||
cv::cvtColor(s_annotatedImage, bgra, cv::COLOR_GRAY2BGRA);
|
||||
s_annotatedImage = bgra;
|
||||
} else if (s_annotatedImage.channels() == 4) {
|
||||
// 已经是 BGRA,无需转换
|
||||
}
|
||||
|
||||
return s_annotatedImage.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放内部缓存
|
||||
*/
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void freeResult() {
|
||||
s_jsonResult.clear();
|
||||
s_jsonResult.shrink_to_fit();
|
||||
s_annotatedImage.release();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user