This commit is contained in:
Ubuntu
2026-07-03 16:58:28 +08:00
commit 32b43f85bd
6240 changed files with 1987678 additions and 0 deletions
+177
View File
@@ -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;
}
+37
View File
@@ -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
);
};
+30
View File
@@ -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%
};
+159
View File
@@ -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;
}
+42
View File
@@ -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
+73
View File
@@ -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_;
};
+92
View File
@@ -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) {}
};
+236
View File
@@ -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"