Compare commits

..

2 Commits

Author SHA1 Message Date
27a24d0073 增加Mat数据获取 2025-03-17 11:51:48 +08:00
84f388f294 增加自动下载 2025-03-17 11:51:23 +08:00
11 changed files with 125 additions and 13 deletions

13
.npmignore Normal file
View File

@ -0,0 +1,13 @@
/.git
/.vscode
/build
/cxx
/node_modules
/src
/test_data
/thirdpart
/.clang-format
/.gitignore
/CMakeLists.txt
/package-lock.json
/tsconfig.json

View File

@ -2,7 +2,7 @@
简单的OpenCV封装
## 编译源码
## 编译源码(可选)
1. 依赖
1. python3
1. cmake
@ -31,6 +31,9 @@ import cv from '@yizhi/opencv';
//配置addon路径
cv.config("ADDON_PATH", "/path/to/cv.node");
//下载addon可选
// 如果配置了ADDON_PATH则下载addon到ADDON_PATH否则下载到build/cv.node
await cv.downloadAddon();
//正常使用
const im = cv.imread("/path/to/input");

View File

@ -50,6 +50,7 @@ class CVMat : public ObjectWrap<CVMat> {
MAT_INSTANCE_METHOD(GetTotal),
MAT_INSTANCE_METHOD(GetTotalWithDim),
MAT_INSTANCE_METHOD(GetSize),
MAT_INSTANCE_METHOD(GetData),
MAT_INSTANCE_METHOD(Col),
MAT_INSTANCE_METHOD(ColRange),
@ -58,6 +59,7 @@ class CVMat : public ObjectWrap<CVMat> {
MAT_INSTANCE_METHOD(Diag),
MAT_INSTANCE_METHOD(Clone),
MAT_INSTANCE_METHOD(CopyTo),
});
constructor = new FunctionReference();
@ -159,7 +161,12 @@ class CVMat : public ObjectWrap<CVMat> {
for (int i = 0; i < mat_.dims; ++i) ret.Set(i, size[i]);
return ret;
}
Napi::Value GetData(const Napi::CallbackInfo &info)
{
auto ptr = mat_.ptr();
auto bytes = mat_.elemSize() * mat_.total();
return ArrayBuffer::New(info.Env(), ptr, bytes);
}
Napi::Value Col(const Napi::CallbackInfo &info)
{
@ -194,7 +201,11 @@ class CVMat : public ObjectWrap<CVMat> {
{
return CreateMat(info.Env(), [&](CVMat &mat) { mat.mat_ = mat_.clone(); });
}
Napi::Value CopyTo(const Napi::CallbackInfo &info) {
auto &target = GetMat(info[0].As<Object>());
mat_.copyTo(target.mat_);
return info.Env().Undefined();
}
static Napi::Value Crop(const Napi::CallbackInfo &info)
{

View File

@ -1,10 +1,11 @@
{
"name": "@yizhi/cv",
"version": "1.0.0",
"version": "1.0.1",
"releaseVersion": "1.0.2",
"main": "dist/index.js",
"types": "typing/index.d.ts",
"scripts": {
"build": "tsc",
"build": "rm -rf dist typing && tsc",
"watch": "tsc -w --inlineSourceMap"
},
"keywords": [],

View File

@ -1,18 +1,21 @@
import path from "path";
import { LINE_8, MARKER_CROSS } from "./consts";
export const config = {
ADDON_PATH: "../../build/cv.node",
ADDON_PATH: path.join(__dirname, "../../build/cv.node"),
DEFAULT_LINE_TYPE: LINE_8,
DEFAULT_THICKNESS: 1,
DEFAULT_MARKER: MARKER_CROSS,
};
export type AddonConfig = typeof config;
export function getAddon() {
return require(config.ADDON_PATH);
}
export function getConfig<N extends keyof typeof config>(name: N): typeof config[N] { return config[name]; }
export function setConfig<N extends keyof typeof config>(name: N, value: typeof config[N]) { config[name] = value; }
export function getConfig<N extends keyof AddonConfig>(name: N): AddonConfig[N] { return config[name]; }
export function setConfig<N extends keyof AddonConfig>(name: N, value: AddonConfig[N]) { config[name] = value; }
export function CVMat() { return getAddon().Mat; }

View File

@ -1,11 +1,10 @@
import { getAddon, getConfig } from "./addon";
export { getConfig as C } from "./addon";
import { Mat } from "./mat";
export type TypedArray = Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | BigUint64Array | BigInt64Array | Float32Array | Float64Array
export function FromCV(mat: any): Mat { return new (Mat as any)(mat); }
export function M(mat: Mat) { return (mat as any).__mat__; }
export function C(name: Parameters<typeof getConfig>[0]) { return getConfig(name); }
export function resolveArgs<R extends Array<any>>(args: any[], checker: boolean | (() => boolean)): R {
if (typeof checker === "function") checker = checker();

77
src/cv/download.ts Normal file
View File

@ -0,0 +1,77 @@
import os from "os";
import fs from "fs";
import path from "path";
import { C } from "./common";
const URLS = {
GITHUB: `https://github.com/kangkang520/node-addons/releases/download/cv{{version}}/cv_{{platform}}_{{arch}}.node`,
URNAS: `http://git.urnas.cn:5200/yizhi-js-lib/opencv/releases/download/{{version}}/cv_{{platform}}_{{arch}}.node`,
}
function getURL(template: string) {
const version = require("../../package.json").releaseVersion;
let platform = "";
let arch = "";
switch (os.platform()) {
case "win32":
platform = "windows";
break;
case "linux":
platform = "linux";
break;
default:
throw new Error(`Unsupported platform: ${os.platform()}, Please compile the addon yourself.`);
}
switch (os.arch()) {
case "x64":
arch = "x64";
break;
default:
throw new Error(`Unsupported architecture: ${os.arch()}, Please compile the addon yourself.`);
}
return template.replaceAll("{{version}}", version).replaceAll("{{platform}}", platform).replaceAll("{{arch}}", arch);
}
async function getStream() {
for (const [name, url] of Object.entries(URLS)) {
try {
return await fetch(getURL(url)).then(res => {
if (res.status != 200) throw new Error("Failed to download addon.");
return res.blob().then(b => b.stream());
})
} catch (e) { }
}
throw new Error("Failed to download addon.");
}
export async function downloadAddon(savename?: string) {
const defaultAddon = path.resolve(process.cwd(), C("ADDON_PATH"));
const saveName = savename ? path.resolve(path.dirname(defaultAddon), savename) : defaultAddon;
if (fs.existsSync(saveName)) return saveName;
await fs.promises.mkdir(path.dirname(saveName), { recursive: true });
const stream = await getStream();
const cacheFile = await new Promise<string>((resolve, reject) => {
const cacheFile = path.join(os.tmpdir(), Date.now() + ".cv.node");
let fsStream!: ReturnType<typeof fs.createWriteStream>;
stream.pipeTo(new WritableStream({
start(controller) {
fsStream = fs.createWriteStream(cacheFile);
},
async write(chunk, controller) {
await new Promise<void>((resolve, reject) => fsStream.write(chunk, err => err ? reject(err) : resolve()));
},
close() {
fsStream.end();
resolve(cacheFile);
},
abort() { }
})).catch(reject);
});
await fs.promises.cp(cacheFile, saveName);
await fs.promises.rm(cacheFile);
return saveName;
}

View File

@ -1,4 +1,5 @@
export { setConfig as config } from "./addon";
export { downloadAddon } from "./download";
export * from "./mat";
export * from "./consts";
export * from "./proc";

View File

@ -35,8 +35,13 @@ export class Mat {
else return this.#mat.GetTotalWithDim(startDim, endDim ?? 2147483647);
}
public get size(): number[] { return this.#mat.GetSize(); }
public get data(): Uint8Array {
if (this.isContinuous) return new Uint8Array(this.#mat.GetData());
else return this.copyTo(this.clone()).data;
}
public clone() { return FromCV(this.#mat.Clone()); }
public copyTo(mat: Mat) { return this.#mat.CopyTo(M(mat)), mat; }
public row(y: number) { return FromCV(this.#mat.Row(y)); }
public col(x: number) { return FromCV(this.#mat.Col(x)); }

View File

@ -2,11 +2,13 @@ import cv from ".";
async function test() {
await cv.downloadAddon();
const fs = await import("fs");
// const buffer = fs.readFileSync("data/im1.jpeg")
const res = await cv.imread("test_data/im1.jpeg");
// const res = await cv.imdecode(buffer);
const cropIm = cv.crop(res, { x: 10, y: 10, width: 300, height: 200 });
console.log(cropIm.data)
console.log(cv.imwrite("test_data/cropIm.jpg", cropIm));
fs.writeFileSync("test_data/base.jpg", cv.imencode(".jpg", res)!);

View File

@ -105,9 +105,6 @@ async function downloadFromURL(name, url, resolver) {
if (!checkFile(saveName)) {
console.log(`开始下载${name}, 地址:${url}`);
await fetch(url).then(res => {
console.log(res.status)
})
const result = spawnSync("curl", ["-o", saveName + ".cache", "-L", url, "-s", "-w", "%{http_code}"], { ...spawnOption, stdio: "pipe" });
assert(result.status == 0 && result.stdout.toString() == "200", `下载${name}失败`);
fs.renameSync(saveName + ".cache", saveName);