463 lines
16 KiB
C++
463 lines
16 KiB
C++
#include "hilog/log.h"
|
||
#include "napi/native_api.h"
|
||
|
||
#include <multimedia/image_framework/image_mdk.h>
|
||
#include <multimedia/image_framework/image_mdk_common.h>
|
||
#include <multimedia/image_framework/image_pixel_map_mdk.h>
|
||
#include <multimedia/image_framework/image_pixel_map_napi.h>
|
||
|
||
#include "PaperRecognizer.h"
|
||
#include "ColorReader.h"
|
||
#include "Types.h"
|
||
#include <bits/alltypes.h>
|
||
#include <opencv2/core/core.hpp>
|
||
#include <opencv2/highgui/highgui.hpp>
|
||
#include <opencv2/opencv.hpp>
|
||
#include <vector>
|
||
|
||
|
||
using namespace cv;
|
||
using namespace std;
|
||
|
||
// ========== 错误码定义 ==========
|
||
// 0 - 成功
|
||
// 1 - 未检测到定位块
|
||
// 2 - 定位块颜色识别失败
|
||
// 3 - 定位块配对失败(一大一小)
|
||
// 4 - 试纸规格匹配失败
|
||
// 5 - 色块位置计算失败
|
||
// 6 - 检测到多条试纸
|
||
// 7 - 图像处理错误
|
||
// 100 - 未知错误
|
||
// ===============================
|
||
|
||
|
||
// https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-image-18
|
||
// https://segmentfault.com/q/1010000045355419
|
||
// https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/image-pixelmap-operation-native
|
||
// https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/image-pixelmap-operation-native-V5
|
||
|
||
//// 假设你有一个cv::Mat对象
|
||
// cv::Mat cvMat = ...; // 假设这是你的OpenCV图像
|
||
//
|
||
//// 转换为PixelMap(伪代码)
|
||
//// 注意:以下代码需要根据你的实际环境进行调整
|
||
// PixelMap* pixelMap = new PixelMap();
|
||
//
|
||
//// 设置PixelMap的大小和格式
|
||
//// 例如,如果cvMat是RGBA格式,你可能需要设置PixelMap的pixelFormat为PixelFormat::RGBA_8888
|
||
// pixelMap->SetSize(cvMat.cols, cvMat.rows);
|
||
//// 设置PixelFormat等(根据cvMat的类型调整)
|
||
//
|
||
//// 分配PixelMap的像素缓冲区(具体API根据HarmonyOS版本而定)
|
||
//// 假设有一个SetPixelBytes方法来设置像素数据
|
||
// uint8_t* buffer = new uint8_t[cvMat.total() * cvMat.elemSize()];
|
||
// memcpy(buffer, cvMat.data, cvMat.total() * cvMat.elemSize());
|
||
// pixelMap->SetPixelBytes(buffer, cvMat.total() * cvMat.elemSize());
|
||
//
|
||
//// 注意:上面的SetPixelBytes方法是伪造的,你需要根据HarmonyOS的API来实现
|
||
//
|
||
//// 现在,pixelMap包含了cvMat的图像数据,可以在ArkTS中使用
|
||
//// ... 将pixelMap传递给ArkTS的UI组件 ...
|
||
//
|
||
//// 清理资源
|
||
// delete[] buffer;
|
||
static napi_value AnalysisImage(napi_env env, napi_callback_info info) {
|
||
|
||
napi_value udfVar = nullptr;
|
||
napi_value pixelMap = nullptr;
|
||
|
||
size_t argc = 2;
|
||
napi_value args[2] = {nullptr};
|
||
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
||
|
||
// napi_value error;
|
||
// napi_create_int32(env, -1, &error);
|
||
|
||
NativePixelMap *native = OH_PixelMap_InitNativePixelMap(env, args[0]);
|
||
if (native == nullptr) {
|
||
return udfVar;
|
||
}
|
||
struct OhosPixelMapInfos pixelMapInfos;
|
||
if (OH_PixelMap_GetImageInfo(native, &pixelMapInfos) != IMAGE_RESULT_SUCCESS) {
|
||
OH_LOG_Print(LOG_APP, LOG_ERROR, 0xFF00, "Test", "Pure : -1");
|
||
return udfVar;
|
||
}
|
||
|
||
void *pixel;
|
||
// 获取NativePixelMap对象的内存地址并锁定内存
|
||
OH_PixelMap_AccessPixels(native, &pixel);
|
||
|
||
// 转换成Mat,注意对齐,所以要传入rowSize
|
||
cv::Mat originMat(pixelMapInfos.height, pixelMapInfos.width, CV_8UC4, pixel, pixelMapInfos.rowSize);
|
||
if (!originMat.data) {
|
||
OH_LOG_Print(LOG_APP, LOG_ERROR, 0xFF00, "Read Image", "Pure : -1");
|
||
return udfVar;
|
||
}
|
||
OH_PixelMap_UnAccessPixels(native);
|
||
|
||
|
||
cv::Mat saveMat;
|
||
cv::cvtColor(originMat, saveMat, cv::COLOR_BGRA2RGBA);
|
||
cv::Mat grayMat;
|
||
cv::cvtColor(saveMat, grayMat, cv::COLOR_RGB2GRAY);
|
||
|
||
|
||
struct OhosPixelMapCreateOps createOps;
|
||
createOps.width = grayMat.cols;
|
||
createOps.height = grayMat.rows;
|
||
createOps.pixelFormat = 1; // 4
|
||
createOps.alphaType = 0;
|
||
size_t bufferSize = createOps.width * createOps.height * 1; // 4
|
||
void *buff = malloc(bufferSize);
|
||
if (buff == nullptr) {
|
||
return udfVar;
|
||
}
|
||
memcpy(buff, grayMat.data, grayMat.total() * grayMat.elemSize());
|
||
|
||
int32_t res = OH_PixelMap_CreatePixelMap(env, createOps, (uint8_t *)buff, bufferSize, &pixelMap);
|
||
free(buff);
|
||
if (res != IMAGE_RESULT_SUCCESS || pixelMap == nullptr) {
|
||
return udfVar;
|
||
}
|
||
return pixelMap;
|
||
|
||
|
||
// opencv默认bgra或bgr,若pixelmap创建时未指定为这两种格式,需要进行格式转换
|
||
// cv::Mat saveMat;
|
||
// cv::cvtColor(originMat, saveMat, cv::COLOR_BGRA2RGBA);
|
||
// char pathArray[1024];
|
||
// size_t length;
|
||
// napi_get_value_string_utf8(env, args[1], pathArray, 1024, &length);
|
||
// std::string path(pathArray);
|
||
// path += "/access.jpg";
|
||
// if (!cv::imwrite(path, saveMat)) {
|
||
// OH_LOG_Print(LOG_APP, LOG_ERROR, 0xFF00, "Write Image", "Pure : -1");
|
||
// return error;
|
||
// }
|
||
//
|
||
// napi_value res;
|
||
// napi_create_int32(env, 1, &res);
|
||
// return res;
|
||
}
|
||
|
||
// ImageLocation imgLoc;
|
||
|
||
static napi_value Add(napi_env env, napi_callback_info info) {
|
||
size_t argc = 2;
|
||
napi_value args[2] = {nullptr};
|
||
|
||
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
||
|
||
napi_valuetype valuetype0;
|
||
napi_typeof(env, args[0], &valuetype0);
|
||
|
||
napi_valuetype valuetype1;
|
||
napi_typeof(env, args[1], &valuetype1);
|
||
|
||
double value0;
|
||
napi_get_value_double(env, args[0], &value0);
|
||
|
||
double value1;
|
||
napi_get_value_double(env, args[1], &value1);
|
||
|
||
cv::Scalar scalar(10);
|
||
scalar[0] = 1;
|
||
scalar[1] = 10;
|
||
scalar[2] = 100;
|
||
scalar[3] = 1000;
|
||
|
||
napi_value sum;
|
||
napi_create_double(env, value0 + value1 + scalar[2], &sum);
|
||
|
||
|
||
return sum;
|
||
}
|
||
|
||
// 全局 PaperRecognizer 实例
|
||
PaperRecognizer g_paperRecognizer;
|
||
|
||
static napi_value GetPaperData(napi_env env, napi_callback_info info) {
|
||
// Create a result object.
|
||
napi_value object = nullptr;
|
||
napi_create_object(env, &object);
|
||
|
||
size_t length;
|
||
Mat paperMat = g_paperRecognizer.getPaperImg();
|
||
length = paperMat.total() * paperMat.elemSize();
|
||
|
||
// parameter 1: byteLength
|
||
napi_value byteLengthValue = nullptr;
|
||
napi_create_uint32(env, length, &byteLengthValue);
|
||
napi_set_named_property(env, object, "byteLength", byteLengthValue);
|
||
|
||
// parameter 2: buffer
|
||
napi_value bufferData;
|
||
void *output_ptr = nullptr;
|
||
napi_create_arraybuffer(env, length, &output_ptr, &bufferData);
|
||
if (output_ptr != nullptr) {
|
||
memcpy(output_ptr, paperMat.data, length);
|
||
} else {
|
||
OH_LOG_ERROR(LOG_APP, "Failed to allocate memory for ArrayBuffer");
|
||
return nullptr;
|
||
}
|
||
napi_set_named_property(env, object, "buffer", bufferData);
|
||
|
||
// parameter 3: width
|
||
napi_value widthValue = nullptr;
|
||
napi_create_uint32(env, paperMat.cols, &widthValue);
|
||
napi_set_named_property(env, object, "width", widthValue);
|
||
|
||
// parameter 4: height
|
||
napi_value heightValue = nullptr;
|
||
napi_create_uint32(env, paperMat.rows, &heightValue);
|
||
napi_set_named_property(env, object, "height", heightValue);
|
||
|
||
return object;
|
||
}
|
||
|
||
// 计算自适应ROI大小(基于定位块高度)
|
||
static int calculateAdaptiveROI(double locatorHeight) {
|
||
// 使用定位块高度的0.6倍作为ROI半边长
|
||
int halfSize = static_cast<int>(locatorHeight * 0.6);
|
||
// 限制在5-15像素范围内(即ROI大小25-225像素)
|
||
halfSize = std::max(5, std::min(15, halfSize));
|
||
return halfSize * 2 + 1; // 返回奇数边长(如11, 21, 31)
|
||
}
|
||
|
||
static napi_value SayHello(napi_env env, napi_callback_info info) {
|
||
size_t argc = 4;
|
||
napi_value args[4] = {nullptr};
|
||
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
||
|
||
napi_valuetype valuetype0;
|
||
napi_typeof(env, args[1], &valuetype0);
|
||
|
||
napi_valuetype valuetype1;
|
||
napi_typeof(env, args[2], &valuetype1);
|
||
|
||
napi_valuetype valuetype2;
|
||
napi_typeof(env, args[3], &valuetype2);
|
||
|
||
int width;
|
||
napi_get_value_int32(env, args[1], &width); // width
|
||
|
||
int height;
|
||
napi_get_value_int32(env, args[2], &height); // height
|
||
|
||
int format;
|
||
napi_get_value_int32(env, args[3], &format); // 8
|
||
|
||
bool result = false;
|
||
napi_status status = napi_is_arraybuffer(env, args[0], &result);
|
||
if (status != napi_ok) {
|
||
napi_throw_error(env, nullptr, "Node-API napi_is_arraybuffer fail");
|
||
return nullptr;
|
||
}
|
||
|
||
void *inputBuffer;
|
||
size_t length;
|
||
napi_get_arraybuffer_info(env, args[0], &inputBuffer, &length);
|
||
|
||
// 转换 NV21 到 RGBA
|
||
cv::Mat yuv_mat(height + height / 2, width, CV_8UC1, inputBuffer);
|
||
cv::Mat rgb_full_mat;//rgb_mat
|
||
cv::Mat t;
|
||
cv::cvtColor(yuv_mat, t, cv::COLOR_YUV2RGBA_NV21);
|
||
cv::rotate(t, rgb_full_mat, cv::ROTATE_90_CLOCKWISE);//rgb_mat
|
||
|
||
//只使用原始图片的一半高度,进行识别,这样会提高识别效率和显示流畅性
|
||
cv::Rect rc_half;
|
||
rc_half.width = rgb_full_mat.cols;
|
||
rc_half.height = rgb_full_mat.rows / 2;
|
||
Mat rgb_mat(rgb_full_mat, rc_half);
|
||
|
||
// 转换为 BGRA 供 PaperRecognizer 使用
|
||
cv::Mat bgra_mat;
|
||
cvtColor(rgb_mat, bgra_mat, COLOR_RGBA2BGRA);
|
||
|
||
// 使用新代码进行识别
|
||
RecognitionResult recResult = g_paperRecognizer.recognize(bgra_mat);
|
||
|
||
// 准备输出图像(标注图)
|
||
cv::Mat outputImage;
|
||
if (!recResult.annotatedImage.empty()) {
|
||
cvtColor(recResult.annotatedImage, outputImage, COLOR_BGRA2RGBA);
|
||
} else {
|
||
outputImage = rgb_mat.clone();
|
||
}
|
||
|
||
// 准备颜色结果字符串
|
||
string strResult = "";
|
||
uint32_t statusCode = 100; // 默认未知错误
|
||
|
||
if (recResult.success) {
|
||
statusCode = 0; // 成功
|
||
|
||
// 使用 ColorReader 读取颜色(白平衡已应用)
|
||
ColorReader colorReader;
|
||
std::vector<ColorResult> colors = colorReader.readColors(
|
||
bgra_mat, recResult.colorBlockCenters, recResult.smallLocatorOnLeft, true);
|
||
|
||
// 计算自适应ROI大小
|
||
double locatorHeight = 0;
|
||
if (!recResult.colorBlockCenters.empty()) {
|
||
// 使用定位块高度估算(从色块中心点间距推算)
|
||
if (recResult.colorBlockCenters.size() >= 2) {
|
||
float dy = std::abs(recResult.colorBlockCenters[1].y - recResult.colorBlockCenters[0].y);
|
||
locatorHeight = dy * 0.8; // 估算定位块高度
|
||
}
|
||
}
|
||
int roiSize = calculateAdaptiveROI(locatorHeight);
|
||
int halfRoi = roiSize / 2;
|
||
|
||
// 重新读取颜色(使用自适应ROI)
|
||
// 注意:PaperRecognizer::calculateColorBlockCenters 已保证
|
||
// centers_[0] 始终靠近小定位块,无论试纸如何放置
|
||
colors.clear();
|
||
for (size_t i = 0; i < recResult.colorBlockCenters.size(); i++) {
|
||
const cv::Point2f& center = recResult.colorBlockCenters[i];
|
||
cv::Rect roi(center.x - halfRoi, center.y - halfRoi, roiSize, roiSize);
|
||
|
||
// 边界检查
|
||
roi.x = std::max(0, roi.x);
|
||
roi.y = std::max(0, roi.y);
|
||
roi.width = std::min(roi.width, bgra_mat.cols - roi.x);
|
||
roi.height = std::min(roi.height, bgra_mat.rows - roi.y);
|
||
|
||
if (roi.width > 0 && roi.height > 0) {
|
||
Mat roiMat(bgra_mat, roi);
|
||
Mat rgbRoi;
|
||
cvtColor(roiMat, rgbRoi, COLOR_BGRA2RGB);
|
||
|
||
Mat hsvMat, labMat;
|
||
cvtColor(rgbRoi, hsvMat, COLOR_RGB2HSV);
|
||
cvtColor(rgbRoi, labMat, COLOR_RGB2Lab);
|
||
|
||
Scalar avgHsv = mean(hsvMat);
|
||
Scalar avgLab = mean(labMat);
|
||
|
||
char buf[100];
|
||
sprintf(buf, "{\"id\":%d,\"value\":\"%.1f,%.1f,%.1f,%.1f,%.1f,%.1f\"}",
|
||
(int)i,
|
||
avgHsv.val[0], avgHsv.val[1], avgHsv.val[2],
|
||
avgLab.val[0], avgLab.val[1], avgLab.val[2]);
|
||
|
||
if (i > 0) strResult += ",";
|
||
strResult += buf;
|
||
}
|
||
}
|
||
strResult = "[" + strResult + "]";
|
||
} else {
|
||
// 映射错误码
|
||
statusCode = recResult.errorCode;
|
||
if (statusCode == 0 && !recResult.success) {
|
||
statusCode = 100; // 未知错误
|
||
}
|
||
}
|
||
|
||
length = outputImage.total() * outputImage.elemSize();
|
||
|
||
// Create a result object.
|
||
napi_value object = nullptr;
|
||
napi_create_object(env, &object);
|
||
|
||
// parameter 1: byteLength
|
||
napi_value byteLengthValue = nullptr;
|
||
napi_create_uint32(env, length, &byteLengthValue);
|
||
napi_set_named_property(env, object, "byteLength", byteLengthValue);
|
||
|
||
// parameter 2: buffer
|
||
napi_value bufferData;
|
||
void *output_ptr = nullptr;
|
||
napi_create_arraybuffer(env, length, &output_ptr, &bufferData);
|
||
if (output_ptr != nullptr) {
|
||
memcpy(output_ptr, outputImage.data, length);
|
||
} else {
|
||
OH_LOG_ERROR(LOG_APP, "Failed to allocate memory for ArrayBuffer");
|
||
return nullptr;
|
||
}
|
||
napi_set_named_property(env, object, "buffer", bufferData);
|
||
|
||
// parameter 3: width
|
||
napi_value widthValue = nullptr;
|
||
napi_create_uint32(env, outputImage.cols, &widthValue);
|
||
napi_set_named_property(env, object, "width", widthValue);
|
||
|
||
// parameter 4: height
|
||
napi_value heightValue = nullptr;
|
||
napi_create_uint32(env, outputImage.rows, &heightValue);
|
||
napi_set_named_property(env, object, "height", heightValue);
|
||
|
||
// parameter 5: strLength
|
||
napi_value strLengthValue = nullptr;
|
||
napi_create_uint32(env, strResult.length(), &strLengthValue);
|
||
napi_set_named_property(env, object, "strLength", strLengthValue);
|
||
|
||
// parameter 6: strBuffer
|
||
if (strResult.length() > 10) {
|
||
napi_value bufferStrData;
|
||
void *output_str_ptr = nullptr;
|
||
napi_create_arraybuffer(env, strResult.length(), &output_str_ptr, &bufferStrData);
|
||
if (output_str_ptr != nullptr) {
|
||
memcpy(output_str_ptr, strResult.data(), strResult.length());
|
||
} else {
|
||
OH_LOG_ERROR(LOG_APP, "Failed to allocate memory for ArrayBuffer 2");
|
||
return nullptr;
|
||
}
|
||
napi_set_named_property(env, object, "strBuffer", bufferStrData);
|
||
}
|
||
|
||
// parameter 7: locatorColor (定位块颜色)
|
||
napi_value locatorColorValue = nullptr;
|
||
napi_create_uint32(env, static_cast<uint32_t>(recResult.locatorColor), &locatorColorValue);
|
||
napi_set_named_property(env, object, "locatorColor", locatorColorValue);
|
||
|
||
// parameter 8: paperType (试纸空间类型)
|
||
napi_value paperTypeValue = nullptr;
|
||
napi_create_uint32(env, static_cast<uint32_t>(recResult.paperType), &paperTypeValue);
|
||
napi_set_named_property(env, object, "paperType", paperTypeValue);
|
||
|
||
// parameter 9: blurLevel (清晰度等级)
|
||
napi_value blurLevelValue = nullptr;
|
||
napi_create_uint32(env, static_cast<uint32_t>(recResult.blurLevel), &blurLevelValue);
|
||
napi_set_named_property(env, object, "blurLevel", blurLevelValue);
|
||
|
||
// parameter 10: blurScore (清晰度分数)
|
||
napi_value blurScoreValue = nullptr;
|
||
napi_create_double(env, recResult.blurScore, &blurScoreValue);
|
||
napi_set_named_property(env, object, "blurScore", blurScoreValue);
|
||
|
||
// parameter 11: status code
|
||
napi_value statusValue = nullptr;
|
||
napi_create_uint32(env, statusCode, &statusValue);
|
||
napi_set_named_property(env, object, "statusCode", statusValue);
|
||
|
||
return object;
|
||
}
|
||
|
||
|
||
EXTERN_C_START
|
||
static napi_value Init(napi_env env, napi_value exports) {
|
||
napi_property_descriptor desc[] = {
|
||
{"analysisImage", nullptr, AnalysisImage, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||
{"sayHello", nullptr, SayHello, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||
{"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||
{"getPaperData", nullptr, GetPaperData, nullptr, nullptr, nullptr, napi_default, nullptr}};
|
||
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
|
||
return exports;
|
||
}
|
||
EXTERN_C_END
|
||
|
||
static napi_module demoModule = {
|
||
.nm_version = 1,
|
||
.nm_flags = 0,
|
||
.nm_filename = nullptr,
|
||
.nm_register_func = Init,
|
||
.nm_modname = "entry",
|
||
.nm_priv = ((void *)0),
|
||
.reserved = {0},
|
||
};
|
||
|
||
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
|