@tzmax/ocr-common
Advanced tools
Comparing version 1.4.1 to 1.4.6
@@ -9,4 +9,4 @@ import type { FileUtils as FileUtilsType, ImageRaw as ImageRawType, InferenceSession as InferenceSessionType, ModelCreateOptions as ModelCreateOptionsType, SplitIntoLineImages as SplitIntoLineImagesType } from '../types/index.js'; | ||
FileUtils: FileUtilsType; | ||
ImageRaw: ImageRawType; | ||
InferenceSession: InferenceSessionType; | ||
ImageRaw: ImageRawType | any; | ||
InferenceSession: InferenceSessionType | any; | ||
splitIntoLineImages: SplitIntoLineImagesType; | ||
@@ -13,0 +13,0 @@ defaultModels: ModelCreateOptionsType['models']; |
@@ -1,14 +0,20 @@ | ||
let FileUtils = undefined; | ||
let ImageRaw = undefined; | ||
let InferenceSession = undefined; | ||
let splitIntoLineImages = undefined; | ||
let defaultModels = undefined; | ||
export function registerBackend(backend) { | ||
FileUtils = backend.FileUtils; | ||
ImageRaw = backend.ImageRaw; | ||
InferenceSession = backend.InferenceSession; | ||
splitIntoLineImages = backend.splitIntoLineImages; | ||
defaultModels = backend.defaultModels; | ||
} | ||
export { FileUtils, ImageRaw, InferenceSession, splitIntoLineImages, defaultModels }; | ||
//# sourceMappingURL=backend.js.map | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.defaultModels = exports.InferenceSession = exports.ImageRaw = exports.FileUtils = void 0; | ||
exports.registerBackend = registerBackend; | ||
exports.splitIntoLineImages = void 0; | ||
var FileUtils = exports.FileUtils = undefined; | ||
var ImageRaw = exports.ImageRaw = undefined; | ||
var InferenceSession = exports.InferenceSession = undefined; | ||
var splitIntoLineImages = exports.splitIntoLineImages = undefined; | ||
var defaultModels = exports.defaultModels = undefined; | ||
function registerBackend(backend) { | ||
exports.FileUtils = FileUtils = backend.FileUtils; | ||
exports.ImageRaw = ImageRaw = backend.ImageRaw; | ||
exports.InferenceSession = InferenceSession = backend.InferenceSession; | ||
exports.splitIntoLineImages = splitIntoLineImages = backend.splitIntoLineImages; | ||
exports.defaultModels = defaultModels = backend.defaultModels; | ||
} |
@@ -1,6 +0,36 @@ | ||
export class FileUtilsBase { | ||
static async read(path) { | ||
throw new Error('Not implemented'); | ||
} | ||
} | ||
//# sourceMappingURL=FileUtilsBase.js.map | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.FileUtilsBase = void 0; | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); | ||
var FileUtilsBase = exports.FileUtilsBase = /*#__PURE__*/function () { | ||
function FileUtilsBase() { | ||
(0, _classCallCheck2["default"])(this, FileUtilsBase); | ||
} | ||
return (0, _createClass2["default"])(FileUtilsBase, null, [{ | ||
key: "read", | ||
value: function () { | ||
var _read = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(path) { | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) switch (_context.prev = _context.next) { | ||
case 0: | ||
throw new Error('Not implemented'); | ||
case 1: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
}, _callee); | ||
})); | ||
function read(_x) { | ||
return _read.apply(this, arguments); | ||
} | ||
return read; | ||
}() | ||
}]); | ||
}(); |
@@ -1,14 +0,34 @@ | ||
export class ImageRawBase { | ||
data; | ||
width; | ||
height; | ||
constructor({ data, width, height }) { | ||
this.data = data; | ||
this.width = width; | ||
this.height = height; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.ImageRawBase = void 0; | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); | ||
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); | ||
var ImageRawBase = exports.ImageRawBase = /*#__PURE__*/function () { | ||
function ImageRawBase(_ref) { | ||
var data = _ref.data, | ||
width = _ref.width, | ||
height = _ref.height; | ||
(0, _classCallCheck2["default"])(this, ImageRawBase); | ||
(0, _defineProperty2["default"])(this, "data", void 0); | ||
(0, _defineProperty2["default"])(this, "width", void 0); | ||
(0, _defineProperty2["default"])(this, "height", void 0); | ||
this.data = data; | ||
this.width = width; | ||
this.height = height; | ||
} | ||
return (0, _createClass2["default"])(ImageRawBase, [{ | ||
key: "getImageRawData", | ||
value: function getImageRawData() { | ||
return { | ||
data: this.data, | ||
width: this.width, | ||
height: this.height | ||
}; | ||
} | ||
getImageRawData() { | ||
return { data: this.data, width: this.width, height: this.height }; | ||
} | ||
} | ||
//# sourceMappingURL=ImageRawBase.js.map | ||
}]); | ||
}(); |
@@ -1,4 +0,38 @@ | ||
export * from './backend.js'; | ||
export * from './FileUtilsBase.js'; | ||
export * from './ImageRawBase.js'; | ||
//# sourceMappingURL=index.js.map | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _backend = require("./backend.js"); | ||
Object.keys(_backend).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _backend[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _backend[key]; | ||
} | ||
}); | ||
}); | ||
var _FileUtilsBase = require("./FileUtilsBase.js"); | ||
Object.keys(_FileUtilsBase).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _FileUtilsBase[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _FileUtilsBase[key]; | ||
} | ||
}); | ||
}); | ||
var _ImageRawBase = require("./ImageRawBase.js"); | ||
Object.keys(_ImageRawBase).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _ImageRawBase[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _ImageRawBase[key]; | ||
} | ||
}); | ||
}); |
@@ -1,231 +0,313 @@ | ||
import cv from '@techstark/opencv-js'; | ||
import clipper from 'js-clipper'; | ||
import { ImageRaw } from '../backend/index.js'; | ||
export async function splitIntoLineImages(image, sourceImage) { | ||
const w = image.width; | ||
const h = image.height; | ||
const srcData = sourceImage; | ||
const edgeRect = []; | ||
const src = cvImread(image); | ||
cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0); | ||
const contours = new cv.MatVector(); | ||
const hierarchy = new cv.Mat(); | ||
cv.findContours(src, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE); | ||
for (let i = 0; i < contours.size(); i++) { | ||
const minSize = 3; | ||
const cnt = contours.get(i); | ||
const { points, sside } = getMiniBoxes(cnt); | ||
if (sside < minSize) | ||
continue; | ||
// TODO sort fast | ||
const clipBox = unclip(points); | ||
const boxMap = cv.matFromArray(clipBox.length / 2, 1, cv.CV_32SC2, clipBox); | ||
const resultObj = getMiniBoxes(boxMap); | ||
const box = resultObj.points; | ||
if (resultObj.sside < minSize + 2) { | ||
continue; | ||
} | ||
function clip(n, min, max) { | ||
return Math.max(min, Math.min(n, max)); | ||
} | ||
const rx = srcData.width / w; | ||
const ry = srcData.height / h; | ||
for (let i = 0; i < box.length; i++) { | ||
box[i][0] *= rx; | ||
box[i][1] *= ry; | ||
} | ||
const box1 = orderPointsClockwise(box); | ||
box1.forEach((item) => { | ||
item[0] = clip(Math.round(item[0]), 0, srcData.width); | ||
item[1] = clip(Math.round(item[1]), 0, srcData.height); | ||
}); | ||
const rect_width = int(linalgNorm(box1[0], box1[1])); | ||
const rect_height = int(linalgNorm(box1[0], box1[3])); | ||
if (rect_width <= 3 || rect_height <= 3) | ||
continue; | ||
const c = getRotateCropImage(srcData, box); | ||
edgeRect.push({ | ||
box, | ||
image: c, | ||
}); | ||
} | ||
src.delete(); | ||
contours.delete(); | ||
hierarchy.delete(); | ||
return edgeRect; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
var _typeof = require("@babel/runtime/helpers/typeof"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.splitIntoLineImages = splitIntoLineImages; | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var cv = _interopRequireWildcard(require("@techstark/opencv-js")); | ||
var clipper = _interopRequireWildcard(require("js-clipper")); | ||
var _index = require("../backend/index.js"); | ||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } | ||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } | ||
function splitIntoLineImages(_x, _x2) { | ||
return _splitIntoLineImages.apply(this, arguments); | ||
} | ||
function _splitIntoLineImages() { | ||
_splitIntoLineImages = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(image, sourceImage) { | ||
var w, h, srcData, edgeRect, src, contours, hierarchy, _loop, _ret, i; | ||
return _regenerator["default"].wrap(function _callee$(_context2) { | ||
while (1) switch (_context2.prev = _context2.next) { | ||
case 0: | ||
w = image.width; | ||
h = image.height; | ||
srcData = sourceImage; | ||
edgeRect = []; | ||
src = cvImread(image); | ||
cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0); | ||
contours = new cv.MatVector(); | ||
hierarchy = new cv.Mat(); | ||
cv.findContours(src, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE); | ||
_loop = /*#__PURE__*/_regenerator["default"].mark(function _loop() { | ||
var minSize, cnt, _getMiniBoxes, points, sside, clipBox, boxMap, resultObj, box, clip, rx, ry, _i, box1, rect_width, rect_height, c; | ||
return _regenerator["default"].wrap(function _loop$(_context) { | ||
while (1) switch (_context.prev = _context.next) { | ||
case 0: | ||
clip = function _clip(n, min, max) { | ||
return Math.max(min, Math.min(n, max)); | ||
}; | ||
minSize = 3; | ||
cnt = contours.get(i); | ||
_getMiniBoxes = getMiniBoxes(cnt), points = _getMiniBoxes.points, sside = _getMiniBoxes.sside; | ||
if (!(sside < minSize)) { | ||
_context.next = 6; | ||
break; | ||
} | ||
return _context.abrupt("return", 0); | ||
case 6: | ||
// TODO sort fast | ||
clipBox = unclip(points); | ||
boxMap = cv.matFromArray(clipBox.length / 2, 1, cv.CV_32SC2, clipBox); | ||
resultObj = getMiniBoxes(boxMap); | ||
box = resultObj.points; | ||
if (!(resultObj.sside < minSize + 2)) { | ||
_context.next = 12; | ||
break; | ||
} | ||
return _context.abrupt("return", 0); | ||
case 12: | ||
rx = srcData.width / w; | ||
ry = srcData.height / h; | ||
for (_i = 0; _i < box.length; _i++) { | ||
box[_i][0] *= rx; | ||
box[_i][1] *= ry; | ||
} | ||
box1 = orderPointsClockwise(box); | ||
box1.forEach(function (item) { | ||
item[0] = clip(Math.round(item[0]), 0, srcData.width); | ||
item[1] = clip(Math.round(item[1]), 0, srcData.height); | ||
}); | ||
rect_width = _int(linalgNorm(box1[0], box1[1])); | ||
rect_height = _int(linalgNorm(box1[0], box1[3])); | ||
if (!(rect_width <= 3 || rect_height <= 3)) { | ||
_context.next = 21; | ||
break; | ||
} | ||
return _context.abrupt("return", 0); | ||
case 21: | ||
c = getRotateCropImage(srcData, box); | ||
edgeRect.push({ | ||
box: box, | ||
image: c | ||
}); | ||
case 23: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
}, _loop); | ||
}); | ||
i = 0; | ||
case 11: | ||
if (!(i < contours.size())) { | ||
_context2.next = 19; | ||
break; | ||
} | ||
return _context2.delegateYield(_loop(), "t0", 13); | ||
case 13: | ||
_ret = _context2.t0; | ||
if (!(_ret === 0)) { | ||
_context2.next = 16; | ||
break; | ||
} | ||
return _context2.abrupt("continue", 16); | ||
case 16: | ||
i++; | ||
_context2.next = 11; | ||
break; | ||
case 19: | ||
src["delete"](); | ||
contours["delete"](); | ||
hierarchy["delete"](); | ||
return _context2.abrupt("return", edgeRect); | ||
case 23: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
}, _callee); | ||
})); | ||
return _splitIntoLineImages.apply(this, arguments); | ||
} | ||
function getMiniBoxes(contour) { | ||
const boundingBox = cv.minAreaRect(contour); | ||
const points = Array.from(boxPoints(boundingBox.center, boundingBox.size, boundingBox.angle)).sort((a, b) => a[0] - b[0]); | ||
let index_1 = 0, index_2 = 1, index_3 = 2, index_4 = 3; | ||
if (points[1][1] > points[0][1]) { | ||
index_1 = 0; | ||
index_4 = 1; | ||
} | ||
else { | ||
index_1 = 1; | ||
index_4 = 0; | ||
} | ||
if (points[3][1] > points[2][1]) { | ||
index_2 = 2; | ||
index_3 = 3; | ||
} | ||
else { | ||
index_2 = 3; | ||
index_3 = 2; | ||
} | ||
const box = [points[index_1], points[index_2], points[index_3], points[index_4]]; | ||
const side = Math.min(boundingBox.size.height, boundingBox.size.width); | ||
return { points: box, sside: side }; | ||
var boundingBox = cv.minAreaRect(contour); | ||
var points = Array.from(boxPoints(boundingBox.center, boundingBox.size, boundingBox.angle)).sort(function (a, b) { | ||
return a[0] - b[0]; | ||
}); | ||
var index_1 = 0, | ||
index_2 = 1, | ||
index_3 = 2, | ||
index_4 = 3; | ||
if (points[1][1] > points[0][1]) { | ||
index_1 = 0; | ||
index_4 = 1; | ||
} else { | ||
index_1 = 1; | ||
index_4 = 0; | ||
} | ||
if (points[3][1] > points[2][1]) { | ||
index_2 = 2; | ||
index_3 = 3; | ||
} else { | ||
index_2 = 3; | ||
index_3 = 2; | ||
} | ||
var box = [points[index_1], points[index_2], points[index_3], points[index_4]]; | ||
var side = Math.min(boundingBox.size.height, boundingBox.size.width); | ||
return { | ||
points: box, | ||
sside: side | ||
}; | ||
} | ||
function unclip(box) { | ||
const unclip_ratio = 1.5; | ||
const area = Math.abs(polygonPolygonArea(box)); | ||
const length = polygonPolygonLength(box); | ||
const distance = (area * unclip_ratio) / length; | ||
const tmpArr = []; | ||
box.forEach((item) => { | ||
const obj = { | ||
X: 0, | ||
Y: 0, | ||
}; | ||
obj.X = item[0]; | ||
obj.Y = item[1]; | ||
tmpArr.push(obj); | ||
}); | ||
const offset = new clipper.ClipperOffset(); | ||
offset.AddPath(tmpArr, clipper.JoinType.jtRound, clipper.EndType.etClosedPolygon); | ||
const expanded = []; | ||
offset.Execute(expanded, distance); | ||
let expandedArr = []; | ||
expanded[0] && | ||
expanded[0].forEach((item) => { | ||
expandedArr.push([item.X, item.Y]); | ||
}); | ||
expandedArr = [].concat(...expandedArr); | ||
return expandedArr; | ||
var _ref; | ||
var unclip_ratio = 1.5; | ||
var area = Math.abs(polygonPolygonArea(box)); | ||
var length = polygonPolygonLength(box); | ||
var distance = area * unclip_ratio / length; | ||
var tmpArr = []; | ||
box.forEach(function (item) { | ||
var obj = { | ||
X: 0, | ||
Y: 0 | ||
}; | ||
obj.X = item[0]; | ||
obj.Y = item[1]; | ||
tmpArr.push(obj); | ||
}); | ||
var offset = new clipper.ClipperOffset(); | ||
offset.AddPath(tmpArr, clipper.JoinType.jtRound, clipper.EndType.etClosedPolygon); | ||
var expanded = []; | ||
offset.Execute(expanded, distance); | ||
var expandedArr = []; | ||
expanded[0] && expanded[0].forEach(function (item) { | ||
expandedArr.push([item.X, item.Y]); | ||
}); | ||
expandedArr = (_ref = []).concat.apply(_ref, (0, _toConsumableArray2["default"])(expandedArr)); | ||
return expandedArr; | ||
} | ||
function orderPointsClockwise(pts) { | ||
const rect = [ | ||
[0, 0], | ||
[0, 0], | ||
[0, 0], | ||
[0, 0], | ||
]; | ||
const s = pts.map((pt) => pt[0] + pt[1]); | ||
rect[0] = pts[s.indexOf(Math.min(...s))]; | ||
rect[2] = pts[s.indexOf(Math.max(...s))]; | ||
const tmp = pts.filter((pt) => pt !== rect[0] && pt !== rect[2]); | ||
const diff = tmp[1].map((e, i) => e - tmp[0][i]); | ||
rect[1] = tmp[diff.indexOf(Math.min(...diff))]; | ||
rect[3] = tmp[diff.indexOf(Math.max(...diff))]; | ||
return rect; | ||
var rect = [[0, 0], [0, 0], [0, 0], [0, 0]]; | ||
var s = pts.map(function (pt) { | ||
return pt[0] + pt[1]; | ||
}); | ||
rect[0] = pts[s.indexOf(Math.min.apply(Math, (0, _toConsumableArray2["default"])(s)))]; | ||
rect[2] = pts[s.indexOf(Math.max.apply(Math, (0, _toConsumableArray2["default"])(s)))]; | ||
var tmp = pts.filter(function (pt) { | ||
return pt !== rect[0] && pt !== rect[2]; | ||
}); | ||
var diff = tmp[1].map(function (e, i) { | ||
return e - tmp[0][i]; | ||
}); | ||
rect[1] = tmp[diff.indexOf(Math.min.apply(Math, (0, _toConsumableArray2["default"])(diff)))]; | ||
rect[3] = tmp[diff.indexOf(Math.max.apply(Math, (0, _toConsumableArray2["default"])(diff)))]; | ||
return rect; | ||
} | ||
function linalgNorm(p0, p1) { | ||
return Math.sqrt(Math.pow(p0[0] - p1[0], 2) + Math.pow(p0[1] - p1[1], 2)); | ||
return Math.sqrt(Math.pow(p0[0] - p1[0], 2) + Math.pow(p0[1] - p1[1], 2)); | ||
} | ||
function int(num) { | ||
return num > 0 ? Math.floor(num) : Math.ceil(num); | ||
function _int(num) { | ||
return num > 0 ? Math.floor(num) : Math.ceil(num); | ||
} | ||
function getRotateCropImage(imageRaw, points) { | ||
const img_crop_width = int(Math.max(linalgNorm(points[0], points[1]), linalgNorm(points[2], points[3]))); | ||
const img_crop_height = int(Math.max(linalgNorm(points[0], points[3]), linalgNorm(points[1], points[2]))); | ||
const pts_std = [ | ||
[0, 0], | ||
[img_crop_width, 0], | ||
[img_crop_width, img_crop_height], | ||
[0, img_crop_height], | ||
]; | ||
const srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, flatten(points)); | ||
const dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, flatten(pts_std)); | ||
// 获取到目标矩阵 | ||
const M = cv.getPerspectiveTransform(srcTri, dstTri); | ||
const src = cvImread(imageRaw); | ||
const dst = new cv.Mat(); | ||
const dsize = new cv.Size(img_crop_width, img_crop_height); | ||
// 透视转换 | ||
cv.warpPerspective(src, dst, M, dsize, cv.INTER_CUBIC, cv.BORDER_REPLICATE, new cv.Scalar()); | ||
const dst_img_height = dst.matSize[0]; | ||
const dst_img_width = dst.matSize[1]; | ||
let dst_rot; | ||
// 图像旋转 | ||
if (dst_img_height / dst_img_width >= 1.5) { | ||
dst_rot = new cv.Mat(); | ||
const dsize_rot = new cv.Size(dst.rows, dst.cols); | ||
const center = new cv.Point(dst.cols / 2, dst.cols / 2); | ||
const M = cv.getRotationMatrix2D(center, 90, 1); | ||
cv.warpAffine(dst, dst_rot, M, dsize_rot, cv.INTER_CUBIC, cv.BORDER_REPLICATE, new cv.Scalar()); | ||
} | ||
src.delete(); | ||
srcTri.delete(); | ||
dstTri.delete(); | ||
if (dst_rot) { | ||
dst.delete(); | ||
} | ||
return cvImshow(dst_rot || dst); | ||
var img_crop_width = _int(Math.max(linalgNorm(points[0], points[1]), linalgNorm(points[2], points[3]))); | ||
var img_crop_height = _int(Math.max(linalgNorm(points[0], points[3]), linalgNorm(points[1], points[2]))); | ||
var pts_std = [[0, 0], [img_crop_width, 0], [img_crop_width, img_crop_height], [0, img_crop_height]]; | ||
var srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, flatten(points)); | ||
var dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, flatten(pts_std)); | ||
// 获取到目标矩阵 | ||
var M = cv.getPerspectiveTransform(srcTri, dstTri); | ||
var src = cvImread(imageRaw); | ||
var dst = new cv.Mat(); | ||
var dsize = new cv.Size(img_crop_width, img_crop_height); | ||
// 透视转换 | ||
cv.warpPerspective(src, dst, M, dsize, cv.INTER_CUBIC, cv.BORDER_REPLICATE, new cv.Scalar()); | ||
var dst_img_height = dst.matSize[0]; | ||
var dst_img_width = dst.matSize[1]; | ||
var dst_rot; | ||
// 图像旋转 | ||
if (dst_img_height / dst_img_width >= 1.5) { | ||
dst_rot = new cv.Mat(); | ||
var dsize_rot = new cv.Size(dst.rows, dst.cols); | ||
var center = new cv.Point(dst.cols / 2, dst.cols / 2); | ||
var _M = cv.getRotationMatrix2D(center, 90, 1); | ||
cv.warpAffine(dst, dst_rot, _M, dsize_rot, cv.INTER_CUBIC, cv.BORDER_REPLICATE, new cv.Scalar()); | ||
} | ||
src["delete"](); | ||
srcTri["delete"](); | ||
dstTri["delete"](); | ||
if (dst_rot) { | ||
dst["delete"](); | ||
} | ||
return cvImshow(dst_rot || dst); | ||
} | ||
function boxPoints(center, size, angle) { | ||
const width = size.width; | ||
const height = size.height; | ||
const theta = (angle * Math.PI) / 180.0; | ||
const cosTheta = Math.cos(theta); | ||
const sinTheta = Math.sin(theta); | ||
const cx = center.x; | ||
const cy = center.y; | ||
const dx = width * 0.5; | ||
const dy = height * 0.5; | ||
const rotatedPoints = []; | ||
// Top-Left | ||
const x1 = cx - dx * cosTheta + dy * sinTheta; | ||
const y1 = cy - dx * sinTheta - dy * cosTheta; | ||
rotatedPoints.push([x1, y1]); | ||
// Top-Right | ||
const x2 = cx + dx * cosTheta + dy * sinTheta; | ||
const y2 = cy + dx * sinTheta - dy * cosTheta; | ||
rotatedPoints.push([x2, y2]); | ||
// Bottom-Right | ||
const x3 = cx + dx * cosTheta - dy * sinTheta; | ||
const y3 = cy + dx * sinTheta + dy * cosTheta; | ||
rotatedPoints.push([x3, y3]); | ||
// Bottom-Left | ||
const x4 = cx - dx * cosTheta - dy * sinTheta; | ||
const y4 = cy - dx * sinTheta + dy * cosTheta; | ||
rotatedPoints.push([x4, y4]); | ||
return rotatedPoints; | ||
var width = size.width; | ||
var height = size.height; | ||
var theta = angle * Math.PI / 180.0; | ||
var cosTheta = Math.cos(theta); | ||
var sinTheta = Math.sin(theta); | ||
var cx = center.x; | ||
var cy = center.y; | ||
var dx = width * 0.5; | ||
var dy = height * 0.5; | ||
var rotatedPoints = []; | ||
// Top-Left | ||
var x1 = cx - dx * cosTheta + dy * sinTheta; | ||
var y1 = cy - dx * sinTheta - dy * cosTheta; | ||
rotatedPoints.push([x1, y1]); | ||
// Top-Right | ||
var x2 = cx + dx * cosTheta + dy * sinTheta; | ||
var y2 = cy + dx * sinTheta - dy * cosTheta; | ||
rotatedPoints.push([x2, y2]); | ||
// Bottom-Right | ||
var x3 = cx + dx * cosTheta - dy * sinTheta; | ||
var y3 = cy + dx * sinTheta + dy * cosTheta; | ||
rotatedPoints.push([x3, y3]); | ||
// Bottom-Left | ||
var x4 = cx - dx * cosTheta - dy * sinTheta; | ||
var y4 = cy - dx * sinTheta + dy * cosTheta; | ||
rotatedPoints.push([x4, y4]); | ||
return rotatedPoints; | ||
} | ||
function polygonPolygonArea(polygon) { | ||
let i = -1, n = polygon.length, a, b = polygon[n - 1], area = 0; | ||
while (++i < n) { | ||
a = b; | ||
b = polygon[i]; | ||
area += a[1] * b[0] - a[0] * b[1]; | ||
} | ||
return area / 2; | ||
var i = -1, | ||
n = polygon.length, | ||
a, | ||
b = polygon[n - 1], | ||
area = 0; | ||
while (++i < n) { | ||
a = b; | ||
b = polygon[i]; | ||
area += a[1] * b[0] - a[0] * b[1]; | ||
} | ||
return area / 2; | ||
} | ||
function polygonPolygonLength(polygon) { | ||
let i = -1, n = polygon.length, b = polygon[n - 1], xa, ya, xb = b[0], yb = b[1], perimeter = 0; | ||
while (++i < n) { | ||
xa = xb; | ||
ya = yb; | ||
b = polygon[i]; | ||
xb = b[0]; | ||
yb = b[1]; | ||
xa -= xb; | ||
ya -= yb; | ||
perimeter += Math.hypot(xa, ya); | ||
} | ||
return perimeter; | ||
var i = -1, | ||
n = polygon.length, | ||
b = polygon[n - 1], | ||
xa, | ||
ya, | ||
xb = b[0], | ||
yb = b[1], | ||
perimeter = 0; | ||
while (++i < n) { | ||
xa = xb; | ||
ya = yb; | ||
b = polygon[i]; | ||
xb = b[0]; | ||
yb = b[1]; | ||
xa -= xb; | ||
ya -= yb; | ||
perimeter += Math.hypot(xa, ya); | ||
} | ||
return perimeter; | ||
} | ||
function flatten(arr) { | ||
return arr | ||
.toString() | ||
.split(',') | ||
.map((item) => +item); | ||
return arr.toString().split(',').map(function (item) { | ||
return +item; | ||
}); | ||
} | ||
function cvImread(image) { | ||
return cv.matFromImageData(image); | ||
return cv.matFromImageData(image); | ||
} | ||
function cvImshow(mat) { | ||
return new ImageRaw({ data: mat.data, width: mat.cols, height: mat.rows }); | ||
} | ||
//# sourceMappingURL=splitIntoLineImages.js.map | ||
return new _index.ImageRaw({ | ||
data: mat.data, | ||
width: mat.cols, | ||
height: mat.rows | ||
}); | ||
} |
@@ -1,6 +0,42 @@ | ||
import { Ocr } from './Ocr.js'; | ||
export default Ocr; | ||
export { registerBackend } from './backend/backend.js'; | ||
export * from './backend/FileUtilsBase.js'; | ||
export * from './backend/ImageRawBase.js'; | ||
//# sourceMappingURL=index.js.map | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _exportNames = { | ||
registerBackend: true | ||
}; | ||
exports["default"] = void 0; | ||
Object.defineProperty(exports, "registerBackend", { | ||
enumerable: true, | ||
get: function get() { | ||
return _backend.registerBackend; | ||
} | ||
}); | ||
var _Ocr = require("./Ocr.js"); | ||
var _backend = require("./backend/backend.js"); | ||
var _FileUtilsBase = require("./backend/FileUtilsBase.js"); | ||
Object.keys(_FileUtilsBase).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; | ||
if (key in exports && exports[key] === _FileUtilsBase[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _FileUtilsBase[key]; | ||
} | ||
}); | ||
}); | ||
var _ImageRawBase = require("./backend/ImageRawBase.js"); | ||
Object.keys(_ImageRawBase).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; | ||
if (key in exports && exports[key] === _ImageRawBase[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _ImageRawBase[key]; | ||
} | ||
}); | ||
}); | ||
var _default = exports["default"] = _Ocr.Ocr; |
@@ -1,73 +0,180 @@ | ||
import invariant from 'tiny-invariant'; | ||
import { ImageRaw, InferenceSession, defaultModels, splitIntoLineImages } from '../backend/index.js'; | ||
import { ModelBase } from './ModelBase.js'; | ||
const BASE_SIZE = 32; | ||
export class Detection extends ModelBase { | ||
static async create({ models, onnxOptions = {}, ...restOptions }) { | ||
const detectionPath = models?.detectionPath || defaultModels?.detectionPath; | ||
invariant(detectionPath, 'detectionPath is required'); | ||
const model = await InferenceSession.create(detectionPath, onnxOptions); | ||
return new Detection({ model, options: restOptions }); | ||
} | ||
async run(path, { onnxOptions = {} } = {}) { | ||
const image = await ImageRaw.open(path); | ||
// Resize image to multiple of 32 | ||
// - image width and height must be a multiple of 32 | ||
// - bigger image -> more accurate result, but takes longer time | ||
// inputImage = await Image.resize(image, multipleOfBaseSize(image, { maxSize: 960 })) | ||
const inputImage = await image.resize(multipleOfBaseSize(image)); | ||
// this.debugImage(inputImage, 'out1-multiple-of-base-size.jpg') | ||
// Covert image data to model data | ||
// - Using `(RGB / 255 - mean) / std` formula | ||
// - omit reshapeOptions (mean/std) is more accurate, can creaet a run option for them | ||
const modelData = this.imageToInput(inputImage, { | ||
// mean: [0.485, 0.456, 0.406], | ||
// std: [0.229, 0.224, 0.225], | ||
}); | ||
// Run the model | ||
console.time('Detection'); | ||
const modelOutput = await this.runModel({ modelData, onnxOptions }); | ||
console.timeEnd('Detection'); | ||
// Convert output data back to image data | ||
// - output value is from 0 to 1, a probability, if value > 0.3, it is a text | ||
// - returns a black and white image | ||
const outputImage = outputToImage(modelOutput, 0.03); | ||
// this.debugImage(outputImage, 'out2-black-white.jpg') | ||
// Find text boxes, split image into lines | ||
// - findContours from the image | ||
// - returns text boxes and line images | ||
const lineImages = await splitIntoLineImages(outputImage, inputImage); | ||
this.debugBoxImage(inputImage, lineImages, 'boxes.jpg'); | ||
return lineImages; | ||
} | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.Detection = void 0; | ||
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); | ||
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); | ||
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); | ||
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); | ||
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); | ||
var _tinyInvariant = _interopRequireDefault(require("tiny-invariant")); | ||
var _index = require("../backend/index.js"); | ||
var _ModelBase2 = require("./ModelBase.js"); | ||
var _excluded = ["models", "onnxOptions"]; | ||
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } | ||
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } | ||
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } | ||
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2["default"])(o), (0, _possibleConstructorReturn2["default"])(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2["default"])(t).constructor) : o.apply(t, e)); } | ||
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } | ||
var BASE_SIZE = 32; | ||
var Detection = exports.Detection = /*#__PURE__*/function (_ModelBase) { | ||
function Detection() { | ||
(0, _classCallCheck2["default"])(this, Detection); | ||
return _callSuper(this, Detection, arguments); | ||
} | ||
(0, _inherits2["default"])(Detection, _ModelBase); | ||
return (0, _createClass2["default"])(Detection, [{ | ||
key: "run", | ||
value: function () { | ||
var _run = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(path) { | ||
var _ref, | ||
_ref$onnxOptions, | ||
onnxOptions, | ||
image, | ||
inputImage, | ||
modelData, | ||
modelOutput, | ||
outputImage, | ||
lineImages, | ||
_args = arguments; | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) switch (_context.prev = _context.next) { | ||
case 0: | ||
_ref = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}, _ref$onnxOptions = _ref.onnxOptions, onnxOptions = _ref$onnxOptions === void 0 ? {} : _ref$onnxOptions; | ||
_context.next = 3; | ||
return _index.ImageRaw.open(path); | ||
case 3: | ||
image = _context.sent; | ||
_context.next = 6; | ||
return image.resize(multipleOfBaseSize(image)); | ||
case 6: | ||
inputImage = _context.sent; | ||
// this.debugImage(inputImage, 'out1-multiple-of-base-size.jpg') | ||
// Covert image data to model data | ||
// - Using `(RGB / 255 - mean) / std` formula | ||
// - omit reshapeOptions (mean/std) is more accurate, can creaet a run option for them | ||
modelData = this.imageToInput(inputImage, { | ||
// mean: [0.485, 0.456, 0.406], | ||
// std: [0.229, 0.224, 0.225], | ||
}); // Run the model | ||
// console.time('Detection') | ||
_context.next = 10; | ||
return this.runModel({ | ||
modelData: modelData, | ||
onnxOptions: onnxOptions | ||
}); | ||
case 10: | ||
modelOutput = _context.sent; | ||
// console.timeEnd('Detection') | ||
// Convert output data back to image data | ||
// - output value is from 0 to 1, a probability, if value > 0.3, it is a text | ||
// - returns a black and white image | ||
outputImage = outputToImage(modelOutput, 0.03); // this.debugImage(outputImage, 'out2-black-white.jpg') | ||
// Find text boxes, split image into lines | ||
// - findContours from the image | ||
// - returns text boxes and line images | ||
_context.next = 14; | ||
return (0, _index.splitIntoLineImages)(outputImage, inputImage); | ||
case 14: | ||
lineImages = _context.sent; | ||
this.debugBoxImage(inputImage, lineImages, 'boxes.jpg'); | ||
return _context.abrupt("return", lineImages); | ||
case 17: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
}, _callee, this); | ||
})); | ||
function run(_x) { | ||
return _run.apply(this, arguments); | ||
} | ||
return run; | ||
}() | ||
}], [{ | ||
key: "create", | ||
value: function () { | ||
var _create = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(_ref2) { | ||
var models, _ref2$onnxOptions, onnxOptions, restOptions, detectionPath, model; | ||
return _regenerator["default"].wrap(function _callee2$(_context2) { | ||
while (1) switch (_context2.prev = _context2.next) { | ||
case 0: | ||
models = _ref2.models, _ref2$onnxOptions = _ref2.onnxOptions, onnxOptions = _ref2$onnxOptions === void 0 ? {} : _ref2$onnxOptions, restOptions = (0, _objectWithoutProperties2["default"])(_ref2, _excluded); | ||
detectionPath = (models === null || models === void 0 ? void 0 : models.detectionPath) || (_index.defaultModels === null || _index.defaultModels === void 0 ? void 0 : _index.defaultModels.detectionPath); | ||
(0, _tinyInvariant["default"])(detectionPath, 'detectionPath is required'); | ||
_context2.next = 5; | ||
return _index.InferenceSession.create(detectionPath, onnxOptions); | ||
case 5: | ||
model = _context2.sent; | ||
return _context2.abrupt("return", new Detection({ | ||
model: model, | ||
options: restOptions | ||
})); | ||
case 7: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
}, _callee2); | ||
})); | ||
function create(_x2) { | ||
return _create.apply(this, arguments); | ||
} | ||
return create; | ||
}() | ||
}]); | ||
}(_ModelBase2.ModelBase); | ||
function multipleOfBaseSize(image) { | ||
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
maxSize = _ref3.maxSize; | ||
var width = image.width; | ||
var height = image.height; | ||
if (maxSize && Math.max(width, height) > maxSize) { | ||
var ratio = width > height ? maxSize / width : maxSize / height; | ||
width = width * ratio; | ||
height = height * ratio; | ||
} | ||
var newWidth = Math.max( | ||
// Math.round | ||
// Math.ceil | ||
Math.ceil(width / BASE_SIZE) * BASE_SIZE, BASE_SIZE); | ||
var newHeight = Math.max(Math.ceil(height / BASE_SIZE) * BASE_SIZE, BASE_SIZE); | ||
return { | ||
width: newWidth, | ||
height: newHeight | ||
}; | ||
} | ||
function multipleOfBaseSize(image, { maxSize } = {}) { | ||
let width = image.width; | ||
let height = image.height; | ||
if (maxSize && Math.max(width, height) > maxSize) { | ||
const ratio = width > height ? maxSize / width : maxSize / height; | ||
width = width * ratio; | ||
height = height * ratio; | ||
} | ||
const newWidth = Math.max( | ||
// Math.round | ||
// Math.ceil | ||
Math.ceil(width / BASE_SIZE) * BASE_SIZE, BASE_SIZE); | ||
const newHeight = Math.max(Math.ceil(height / BASE_SIZE) * BASE_SIZE, BASE_SIZE); | ||
return { width: newWidth, height: newHeight }; | ||
} | ||
function outputToImage(output, threshold) { | ||
const height = output.dims[2]; | ||
const width = output.dims[3]; | ||
const data = new Uint8Array(width * height * 4); | ||
for (const [outIndex, outValue] of output.data.entries()) { | ||
const n = outIndex * 4; | ||
const value = outValue > threshold ? 255 : 0; | ||
data[n] = value; // R | ||
data[n + 1] = value; // G | ||
data[n + 2] = value; // B | ||
data[n + 3] = 255; // A | ||
var height = output.dims[2]; | ||
var width = output.dims[3]; | ||
var data = new Uint8Array(width * height * 4); | ||
var _iterator = _createForOfIteratorHelper(output.data.entries()), | ||
_step; | ||
try { | ||
for (_iterator.s(); !(_step = _iterator.n()).done;) { | ||
var _step$value = (0, _slicedToArray2["default"])(_step.value, 2), | ||
outIndex = _step$value[0], | ||
outValue = _step$value[1]; | ||
var n = outIndex * 4; | ||
var value = outValue > threshold ? 255 : 0; | ||
data[n] = value; // R | ||
data[n + 1] = value; // G | ||
data[n + 2] = value; // B | ||
data[n + 3] = 255; // A | ||
} | ||
return new ImageRaw({ data, width, height }); | ||
} | ||
//# sourceMappingURL=Detection.js.map | ||
} catch (err) { | ||
_iterator.e(err); | ||
} finally { | ||
_iterator.f(); | ||
} | ||
return new _index.ImageRaw({ | ||
data: data, | ||
width: width, | ||
height: height | ||
}); | ||
} |
@@ -1,3 +0,27 @@ | ||
export * from './Detection.js'; | ||
export * from './Recognition.js'; | ||
//# sourceMappingURL=index.js.map | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _Detection = require("./Detection.js"); | ||
Object.keys(_Detection).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _Detection[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _Detection[key]; | ||
} | ||
}); | ||
}); | ||
var _Recognition = require("./Recognition.js"); | ||
Object.keys(_Recognition).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _Recognition[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _Recognition[key]; | ||
} | ||
}); | ||
}); |
@@ -1,53 +0,129 @@ | ||
import { Tensor } from 'onnxruntime-common'; | ||
export class ModelBase { | ||
options; | ||
#model; | ||
constructor({ model, options }) { | ||
this.#model = model; | ||
this.options = options; | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.ModelBase = void 0; | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); | ||
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); | ||
var _onnxruntimeCommon = require("onnxruntime-common"); | ||
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } | ||
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } | ||
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } | ||
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } | ||
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } | ||
var _model = /*#__PURE__*/new WeakMap(); | ||
var _ModelBase_brand = /*#__PURE__*/new WeakSet(); | ||
var ModelBase = exports.ModelBase = /*#__PURE__*/function () { | ||
function ModelBase(_ref) { | ||
var model = _ref.model, | ||
options = _ref.options; | ||
(0, _classCallCheck2["default"])(this, ModelBase); | ||
_classPrivateMethodInitSpec(this, _ModelBase_brand); | ||
(0, _defineProperty2["default"])(this, "options", void 0); | ||
_classPrivateFieldInitSpec(this, _model, void 0); | ||
_classPrivateFieldSet(_model, this, model); | ||
this.options = options; | ||
} | ||
return (0, _createClass2["default"])(ModelBase, [{ | ||
key: "runModel", | ||
value: function () { | ||
var _runModel = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(_ref2) { | ||
var modelData, _ref2$onnxOptions, onnxOptions, input, outputs, output; | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) switch (_context.prev = _context.next) { | ||
case 0: | ||
modelData = _ref2.modelData, _ref2$onnxOptions = _ref2.onnxOptions, onnxOptions = _ref2$onnxOptions === void 0 ? {} : _ref2$onnxOptions; | ||
input = _assertClassBrand(_ModelBase_brand, this, _prepareInput).call(this, modelData); | ||
_context.next = 4; | ||
return _classPrivateFieldGet(_model, this).run((0, _defineProperty2["default"])({}, _classPrivateFieldGet(_model, this).inputNames[0], input), onnxOptions); | ||
case 4: | ||
outputs = _context.sent; | ||
output = outputs[_classPrivateFieldGet(_model, this).outputNames[0]]; | ||
return _context.abrupt("return", output); | ||
case 7: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
}, _callee, this); | ||
})); | ||
function runModel(_x) { | ||
return _runModel.apply(this, arguments); | ||
} | ||
return runModel; | ||
}() | ||
}, { | ||
key: "imageToInput", | ||
value: function imageToInput(image, _ref3) { | ||
var _ref3$mean = _ref3.mean, | ||
mean = _ref3$mean === void 0 ? [0, 0, 0] : _ref3$mean, | ||
_ref3$std = _ref3.std, | ||
std = _ref3$std === void 0 ? [1, 1, 1] : _ref3$std; | ||
var R = []; | ||
var G = []; | ||
var B = []; | ||
for (var i = 0; i < image.data.length; i += 4) { | ||
R.push((image.data[i] / 255 - mean[0]) / std[0]); | ||
G.push((image.data[i + 1] / 255 - mean[1]) / std[1]); | ||
B.push((image.data[i + 2] / 255 - mean[2]) / std[2]); | ||
} | ||
var newData = [].concat(B, G, R); | ||
return { | ||
data: newData, | ||
width: image.width, | ||
height: image.height | ||
}; | ||
} | ||
async runModel({ modelData, onnxOptions = {}, }) { | ||
const input = this.#prepareInput(modelData); | ||
const outputs = await this.#model.run({ | ||
[this.#model.inputNames[0]]: input, | ||
}, onnxOptions); | ||
const output = outputs[this.#model.outputNames[0]]; | ||
return output; | ||
}, { | ||
key: "debugImage", | ||
value: function debugImage(image, path) { | ||
var _this$options = this.options, | ||
debugOutputDir = _this$options.debugOutputDir, | ||
isDebug = _this$options.isDebug; | ||
if (!isDebug || !debugOutputDir) { | ||
return; | ||
} | ||
image.write("".concat(debugOutputDir, "/").concat(path)); | ||
} | ||
#prepareInput(modelData) { | ||
const input = Float32Array.from(modelData.data); | ||
return new Tensor('float32', input, [1, 3, modelData.height, modelData.width]); | ||
} | ||
imageToInput(image, { mean = [0, 0, 0], std = [1, 1, 1] }) { | ||
const R = []; | ||
const G = []; | ||
const B = []; | ||
for (let i = 0; i < image.data.length; i += 4) { | ||
R.push((image.data[i] / 255 - mean[0]) / std[0]); | ||
G.push((image.data[i + 1] / 255 - mean[1]) / std[1]); | ||
B.push((image.data[i + 2] / 255 - mean[2]) / std[2]); | ||
} | ||
const newData = [...B, ...G, ...R]; | ||
return { | ||
data: newData, | ||
width: image.width, | ||
height: image.height, | ||
}; | ||
} | ||
debugImage(image, path) { | ||
const { debugOutputDir, isDebug } = this.options; | ||
if (!isDebug || !debugOutputDir) { | ||
return; | ||
} | ||
image.write(`${debugOutputDir}/${path}`); | ||
} | ||
async debugBoxImage(sourceImage, lineImages, path) { | ||
const { debugOutputDir, isDebug } = this.options; | ||
if (!isDebug || !debugOutputDir) { | ||
return; | ||
} | ||
const boxImage = await sourceImage.drawBox(lineImages); | ||
boxImage.write(`${debugOutputDir}/${path}`); | ||
} | ||
} | ||
//# sourceMappingURL=ModelBase.js.map | ||
}, { | ||
key: "debugBoxImage", | ||
value: function () { | ||
var _debugBoxImage = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(sourceImage, lineImages, path) { | ||
var _this$options2, debugOutputDir, isDebug, boxImage; | ||
return _regenerator["default"].wrap(function _callee2$(_context2) { | ||
while (1) switch (_context2.prev = _context2.next) { | ||
case 0: | ||
_this$options2 = this.options, debugOutputDir = _this$options2.debugOutputDir, isDebug = _this$options2.isDebug; | ||
if (!(!isDebug || !debugOutputDir)) { | ||
_context2.next = 3; | ||
break; | ||
} | ||
return _context2.abrupt("return"); | ||
case 3: | ||
_context2.next = 5; | ||
return sourceImage.drawBox(lineImages); | ||
case 5: | ||
boxImage = _context2.sent; | ||
boxImage.write("".concat(debugOutputDir, "/").concat(path)); | ||
case 7: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
}, _callee2, this); | ||
})); | ||
function debugBoxImage(_x2, _x3, _x4) { | ||
return _debugBoxImage.apply(this, arguments); | ||
} | ||
return debugBoxImage; | ||
}() | ||
}]); | ||
}(); | ||
function _prepareInput(modelData) { | ||
var input = Float32Array.from(modelData.data); | ||
return new _onnxruntimeCommon.Tensor('float32', input, [1, 3, modelData.height, modelData.width]); | ||
} |
@@ -1,190 +0,406 @@ | ||
import invariant from 'tiny-invariant'; | ||
import { FileUtils, InferenceSession, defaultModels } from '../backend/index.js'; | ||
import { ModelBase } from './ModelBase.js'; | ||
export class Recognition extends ModelBase { | ||
#dictionary; | ||
static async create({ models, onnxOptions = {}, ...restOptions }) { | ||
const recognitionPath = models?.recognitionPath || defaultModels?.recognitionPath; | ||
invariant(recognitionPath, 'recognitionPath is required'); | ||
const dictionaryPath = models?.dictionaryPath || defaultModels?.dictionaryPath; | ||
invariant(dictionaryPath, 'dictionaryPath is required'); | ||
const model = await InferenceSession.create(recognitionPath, onnxOptions); | ||
const dictionaryText = await FileUtils.read(dictionaryPath); | ||
const dictionary = [...dictionaryText.split('\n'), ' ']; | ||
return new Recognition({ model, options: restOptions }, dictionary); | ||
} | ||
constructor(options, dictionary) { | ||
super(options); | ||
this.#dictionary = dictionary; | ||
} | ||
async run(lineImages, { onnxOptions = {}, isFormatByLine = true } = {}) { | ||
const modelDatas = await Promise.all( | ||
// Detect text from each line image | ||
lineImages.map(async (lineImage, index) => { | ||
// Resize Image to 48px height | ||
// - height must <= 48 | ||
// - height: 48 is more accurate then 40, but same as 30 | ||
const image = await lineImage.image.resize({ | ||
height: 48, | ||
}); | ||
// this.debugImage(lineImage.image, `./output/out9-line-${index}.jpg`) | ||
// this.debugImage(image, `./output/out9-line-${index}-resized.jpg`) | ||
// transform image data to model data | ||
const modelData = this.imageToInput(image, { | ||
// mean: [0.5, 0.5, 0.5], | ||
// std: [0.5, 0.5, 0.5], | ||
}); | ||
return modelData; | ||
})); | ||
const allLines = []; | ||
console.time('Recognition'); | ||
for (const modelData of modelDatas) { | ||
// Run model for each line image | ||
const output = await this.runModel({ modelData, onnxOptions }); | ||
// use Dictoinary to decode output to text | ||
const lines = await this.decodeText(output); | ||
allLines.unshift(...lines); | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.Recognition = void 0; | ||
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); | ||
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); | ||
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); | ||
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); | ||
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); | ||
var _tinyInvariant = _interopRequireDefault(require("tiny-invariant")); | ||
var _index = require("../backend/index.js"); | ||
var _ModelBase2 = require("./ModelBase.js"); | ||
var _excluded = ["models", "onnxOptions"]; | ||
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } | ||
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } | ||
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } | ||
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2["default"])(o), (0, _possibleConstructorReturn2["default"])(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2["default"])(t).constructor) : o.apply(t, e)); } | ||
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } | ||
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } | ||
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } | ||
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } | ||
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } | ||
var _dictionary = /*#__PURE__*/new WeakMap(); | ||
var Recognition = exports.Recognition = /*#__PURE__*/function (_ModelBase) { | ||
function Recognition(options, dictionary) { | ||
var _this; | ||
(0, _classCallCheck2["default"])(this, Recognition); | ||
_this = _callSuper(this, Recognition, [options]); | ||
_classPrivateFieldInitSpec(_this, _dictionary, void 0); | ||
_classPrivateFieldSet(_dictionary, _this, dictionary); | ||
return _this; | ||
} | ||
(0, _inherits2["default"])(Recognition, _ModelBase); | ||
return (0, _createClass2["default"])(Recognition, [{ | ||
key: "run", | ||
value: function () { | ||
var _run = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(lineImages) { | ||
var _this2 = this; | ||
var _ref, | ||
_ref$onnxOptions, | ||
onnxOptions, | ||
_ref$isFormatByLine, | ||
isFormatByLine, | ||
modelDatas, | ||
allLines, | ||
_iterator, | ||
_step, | ||
modelData, | ||
output, | ||
lines, | ||
result, | ||
_args2 = arguments; | ||
return _regenerator["default"].wrap(function _callee2$(_context2) { | ||
while (1) switch (_context2.prev = _context2.next) { | ||
case 0: | ||
_ref = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {}, _ref$onnxOptions = _ref.onnxOptions, onnxOptions = _ref$onnxOptions === void 0 ? {} : _ref$onnxOptions, _ref$isFormatByLine = _ref.isFormatByLine, isFormatByLine = _ref$isFormatByLine === void 0 ? true : _ref$isFormatByLine; | ||
_context2.next = 3; | ||
return Promise.all( | ||
// Detect text from each line image | ||
lineImages.map(/*#__PURE__*/function () { | ||
var _ref2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(lineImage, index) { | ||
var image, modelData; | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) switch (_context.prev = _context.next) { | ||
case 0: | ||
_context.next = 2; | ||
return lineImage.image.resize({ | ||
height: 48 | ||
}); | ||
case 2: | ||
image = _context.sent; | ||
// this.debugImage(lineImage.image, `./output/out9-line-${index}.jpg`) | ||
// this.debugImage(image, `./output/out9-line-${index}-resized.jpg`) | ||
// transform image data to model data | ||
modelData = _this2.imageToInput(image, { | ||
// mean: [0.5, 0.5, 0.5], | ||
// std: [0.5, 0.5, 0.5], | ||
}); | ||
return _context.abrupt("return", modelData); | ||
case 5: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
}, _callee); | ||
})); | ||
return function (_x2, _x3) { | ||
return _ref2.apply(this, arguments); | ||
}; | ||
}())); | ||
case 3: | ||
modelDatas = _context2.sent; | ||
allLines = []; // console.time('Recognition') | ||
_iterator = _createForOfIteratorHelper(modelDatas); | ||
_context2.prev = 6; | ||
_iterator.s(); | ||
case 8: | ||
if ((_step = _iterator.n()).done) { | ||
_context2.next = 19; | ||
break; | ||
} | ||
modelData = _step.value; | ||
_context2.next = 12; | ||
return this.runModel({ | ||
modelData: modelData, | ||
onnxOptions: onnxOptions | ||
}); | ||
case 12: | ||
output = _context2.sent; | ||
_context2.next = 15; | ||
return this.decodeText(output); | ||
case 15: | ||
lines = _context2.sent; | ||
allLines.unshift.apply(allLines, (0, _toConsumableArray2["default"])(lines)); | ||
case 17: | ||
_context2.next = 8; | ||
break; | ||
case 19: | ||
_context2.next = 24; | ||
break; | ||
case 21: | ||
_context2.prev = 21; | ||
_context2.t0 = _context2["catch"](6); | ||
_iterator.e(_context2.t0); | ||
case 24: | ||
_context2.prev = 24; | ||
_iterator.f(); | ||
return _context2.finish(24); | ||
case 27: | ||
console.timeEnd('Recognition'); | ||
result = calculateBox({ | ||
lines: allLines, | ||
lineImages: lineImages, | ||
isFormatByLine: isFormatByLine | ||
}); | ||
return _context2.abrupt("return", result); | ||
case 30: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
}, _callee2, this, [[6, 21, 24, 27]]); | ||
})); | ||
function run(_x) { | ||
return _run.apply(this, arguments); | ||
} | ||
return run; | ||
}() | ||
}, { | ||
key: "decodeText", | ||
value: function decodeText(output) { | ||
var data = output; | ||
var predLen = data.dims[2]; | ||
var line = []; | ||
var ml = data.dims[0] - 1; | ||
for (var l = 0; l < data.data.length; l += predLen * data.dims[1]) { | ||
var predsIdx = []; | ||
var predsProb = []; | ||
for (var i = l; i < l + predLen * data.dims[1]; i += predLen) { | ||
var tmpArr = data.data.slice(i, i + predLen); | ||
var tmpMax = tmpArr.reduce(function (a, b) { | ||
return Math.max(a, b); | ||
}, Number.NEGATIVE_INFINITY); | ||
var tmpIdx = tmpArr.indexOf(tmpMax); | ||
predsProb.push(tmpMax); | ||
predsIdx.push(tmpIdx); | ||
} | ||
console.timeEnd('Recognition'); | ||
const result = calculateBox({ lines: allLines, lineImages, isFormatByLine }); | ||
return result; | ||
line[ml] = decode(_classPrivateFieldGet(_dictionary, this), predsIdx, predsProb, true); | ||
ml--; | ||
} | ||
return line; | ||
} | ||
decodeText(output) { | ||
const data = output; | ||
const predLen = data.dims[2]; | ||
const line = []; | ||
let ml = data.dims[0] - 1; | ||
for (let l = 0; l < data.data.length; l += predLen * data.dims[1]) { | ||
const predsIdx = []; | ||
const predsProb = []; | ||
for (let i = l; i < l + predLen * data.dims[1]; i += predLen) { | ||
const tmpArr = data.data.slice(i, i + predLen); | ||
const tmpMax = tmpArr.reduce((a, b) => Math.max(a, b), Number.NEGATIVE_INFINITY); | ||
const tmpIdx = tmpArr.indexOf(tmpMax); | ||
predsProb.push(tmpMax); | ||
predsIdx.push(tmpIdx); | ||
} | ||
line[ml] = decode(this.#dictionary, predsIdx, predsProb, true); | ||
ml--; | ||
} | ||
return line; | ||
} | ||
} | ||
}], [{ | ||
key: "create", | ||
value: function () { | ||
var _create = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3(_ref3) { | ||
var models, _ref3$onnxOptions, onnxOptions, restOptions, recognitionPath, dictionaryPath, model, dictionaryText, dictionary; | ||
return _regenerator["default"].wrap(function _callee3$(_context3) { | ||
while (1) switch (_context3.prev = _context3.next) { | ||
case 0: | ||
models = _ref3.models, _ref3$onnxOptions = _ref3.onnxOptions, onnxOptions = _ref3$onnxOptions === void 0 ? {} : _ref3$onnxOptions, restOptions = (0, _objectWithoutProperties2["default"])(_ref3, _excluded); | ||
recognitionPath = (models === null || models === void 0 ? void 0 : models.recognitionPath) || (_index.defaultModels === null || _index.defaultModels === void 0 ? void 0 : _index.defaultModels.recognitionPath); | ||
(0, _tinyInvariant["default"])(recognitionPath, 'recognitionPath is required'); | ||
dictionaryPath = (models === null || models === void 0 ? void 0 : models.dictionaryPath) || (_index.defaultModels === null || _index.defaultModels === void 0 ? void 0 : _index.defaultModels.dictionaryPath); | ||
(0, _tinyInvariant["default"])(dictionaryPath, 'dictionaryPath is required'); | ||
_context3.next = 7; | ||
return _index.InferenceSession.create(recognitionPath, onnxOptions); | ||
case 7: | ||
model = _context3.sent; | ||
_context3.next = 10; | ||
return _index.FileUtils.read(dictionaryPath); | ||
case 10: | ||
dictionaryText = _context3.sent; | ||
dictionary = [].concat((0, _toConsumableArray2["default"])(dictionaryText.split('\n')), [' ']); | ||
return _context3.abrupt("return", new Recognition({ | ||
model: model, | ||
options: restOptions | ||
}, dictionary)); | ||
case 13: | ||
case "end": | ||
return _context3.stop(); | ||
} | ||
}, _callee3); | ||
})); | ||
function create(_x4) { | ||
return _create.apply(this, arguments); | ||
} | ||
return create; | ||
}() | ||
}]); | ||
}(_ModelBase2.ModelBase); | ||
function decode(dictionary, textIndex, textProb, isRemoveDuplicate) { | ||
const ignoredTokens = [0]; | ||
const charList = []; | ||
const confList = []; | ||
for (let idx = 0; idx < textIndex.length; idx++) { | ||
if (textIndex[idx] in ignoredTokens) { | ||
continue; | ||
} | ||
if (isRemoveDuplicate) { | ||
if (idx > 0 && textIndex[idx - 1] === textIndex[idx]) { | ||
continue; | ||
} | ||
} | ||
charList.push(dictionary[textIndex[idx] - 1]); | ||
if (textProb) { | ||
confList.push(textProb[idx]); | ||
} | ||
else { | ||
confList.push(1); | ||
} | ||
var ignoredTokens = [0]; | ||
var charList = []; | ||
var confList = []; | ||
for (var idx = 0; idx < textIndex.length; idx++) { | ||
if (textIndex[idx] in ignoredTokens) { | ||
continue; | ||
} | ||
let text = ''; | ||
let mean = 0; | ||
if (charList.length) { | ||
text = charList.join(''); | ||
let sum = 0; | ||
confList.forEach((item) => { | ||
sum += item; | ||
}); | ||
mean = sum / confList.length; | ||
if (isRemoveDuplicate) { | ||
if (idx > 0 && textIndex[idx - 1] === textIndex[idx]) { | ||
continue; | ||
} | ||
} | ||
return { text, mean }; | ||
charList.push(dictionary[textIndex[idx] - 1]); | ||
if (textProb) { | ||
confList.push(textProb[idx]); | ||
} else { | ||
confList.push(1); | ||
} | ||
} | ||
var text = ''; | ||
var mean = 0; | ||
if (charList.length) { | ||
text = charList.join(''); | ||
var sum = 0; | ||
confList.forEach(function (item) { | ||
sum += item; | ||
}); | ||
mean = sum / confList.length; | ||
} | ||
return { | ||
text: text, | ||
mean: mean | ||
}; | ||
} | ||
function calculateBox({ lines, lineImages, isFormatByLine = true }) { | ||
let mainLine = lines; | ||
const box = lineImages; | ||
for (const i in mainLine) { | ||
const b = box[mainLine.length - Number(i) - 1].box; | ||
for (const p of b) { | ||
p[0] = p[0]; | ||
p[1] = p[1]; | ||
} | ||
mainLine[i]['box'] = b; | ||
function calculateBox(_ref4) { | ||
var lines = _ref4.lines, | ||
lineImages = _ref4.lineImages, | ||
_ref4$isFormatByLine = _ref4.isFormatByLine, | ||
isFormatByLine = _ref4$isFormatByLine === void 0 ? true : _ref4$isFormatByLine; | ||
var mainLine = lines; | ||
var box = lineImages; | ||
for (var i in mainLine) { | ||
var b = box[mainLine.length - Number(i) - 1].box; | ||
var _iterator2 = _createForOfIteratorHelper(b), | ||
_step2; | ||
try { | ||
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { | ||
var p = _step2.value; | ||
p[0] = p[0]; | ||
p[1] = p[1]; | ||
} | ||
} catch (err) { | ||
_iterator2.e(err); | ||
} finally { | ||
_iterator2.f(); | ||
} | ||
mainLine = mainLine.filter((x) => x.mean >= 0.5); | ||
if (isFormatByLine) { | ||
mainLine = afAfRec(mainLine); | ||
} | ||
return mainLine; | ||
mainLine[i]['box'] = b; | ||
} | ||
mainLine = mainLine.filter(function (x) { | ||
return x.mean >= 0.5; | ||
}); | ||
if (isFormatByLine) { | ||
mainLine = afAfRec(mainLine); | ||
} | ||
return mainLine; | ||
} | ||
function afAfRec(l) { | ||
const line = []; | ||
const ind = new Map(); | ||
for (const i in l) { | ||
let item = l[i].box; | ||
ind.set(item, Number(i)); | ||
var line = []; | ||
var ind = new Map(); | ||
for (var i in l) { | ||
var item = l[i].box; | ||
ind.set(item, Number(i)); | ||
} | ||
function calculateAverageHeight(boxes) { | ||
var totalHeight = 0; | ||
var _iterator3 = _createForOfIteratorHelper(boxes), | ||
_step3; | ||
try { | ||
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { | ||
var box = _step3.value; | ||
var _box = (0, _slicedToArray2["default"])(box, 3), | ||
_box$ = (0, _slicedToArray2["default"])(_box[0], 2), | ||
y1 = _box$[1], | ||
_box$2 = (0, _slicedToArray2["default"])(_box[2], 2), | ||
y2 = _box$2[1]; | ||
var height = y2 - y1; | ||
totalHeight += height; | ||
} | ||
} catch (err) { | ||
_iterator3.e(err); | ||
} finally { | ||
_iterator3.f(); | ||
} | ||
function calculateAverageHeight(boxes) { | ||
let totalHeight = 0; | ||
for (const box of boxes) { | ||
const [[, y1], , [, y2]] = box; | ||
const height = y2 - y1; | ||
totalHeight += height; | ||
return totalHeight / boxes.length; | ||
} | ||
function groupBoxesByMidlineDifference(boxes) { | ||
var averageHeight = calculateAverageHeight(boxes); | ||
var result = []; | ||
var _iterator4 = _createForOfIteratorHelper(boxes), | ||
_step4; | ||
try { | ||
var _loop = function _loop() { | ||
var box = _step4.value; | ||
var _box2 = (0, _slicedToArray2["default"])(box, 3), | ||
_box2$ = (0, _slicedToArray2["default"])(_box2[0], 2), | ||
y1 = _box2$[1], | ||
_box2$2 = (0, _slicedToArray2["default"])(_box2[2], 2), | ||
y2 = _box2$2[1]; | ||
var midline = (y1 + y2) / 2; | ||
var group = result.find(function (b) { | ||
var _b$ = (0, _slicedToArray2["default"])(b[0], 3), | ||
_b$$ = (0, _slicedToArray2["default"])(_b$[0], 2), | ||
groupY1 = _b$$[1], | ||
_b$$2 = (0, _slicedToArray2["default"])(_b$[2], 2), | ||
groupY2 = _b$$2[1]; | ||
var groupMidline = (groupY1 + groupY2) / 2; | ||
return Math.abs(groupMidline - midline) < averageHeight / 2; | ||
}); | ||
if (group) { | ||
group.push(box); | ||
} else { | ||
result.push([box]); | ||
} | ||
return totalHeight / boxes.length; | ||
}; | ||
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { | ||
_loop(); | ||
} | ||
} catch (err) { | ||
_iterator4.e(err); | ||
} finally { | ||
_iterator4.f(); | ||
} | ||
function groupBoxesByMidlineDifference(boxes) { | ||
const averageHeight = calculateAverageHeight(boxes); | ||
const result = []; | ||
for (const box of boxes) { | ||
const [[, y1], , [, y2]] = box; | ||
const midline = (y1 + y2) / 2; | ||
const group = result.find((b) => { | ||
const [[, groupY1], , [, groupY2]] = b[0]; | ||
const groupMidline = (groupY1 + groupY2) / 2; | ||
return Math.abs(groupMidline - midline) < averageHeight / 2; | ||
}); | ||
if (group) { | ||
group.push(box); | ||
} | ||
else { | ||
result.push([box]); | ||
} | ||
} | ||
for (const group of result) { | ||
group.sort((a, b) => { | ||
const [ltA] = a; | ||
const [ltB] = b; | ||
return ltA[0] - ltB[0]; | ||
}); | ||
} | ||
result.sort((a, b) => a[0][0][1] - b[0][0][1]); | ||
return result; | ||
for (var _i = 0, _result = result; _i < _result.length; _i++) { | ||
var group = _result[_i]; | ||
group.sort(function (a, b) { | ||
var _a = (0, _slicedToArray2["default"])(a, 1), | ||
ltA = _a[0]; | ||
var _b = (0, _slicedToArray2["default"])(b, 1), | ||
ltB = _b[0]; | ||
return ltA[0] - ltB[0]; | ||
}); | ||
} | ||
const boxes = groupBoxesByMidlineDifference([...ind.keys()]); | ||
for (const i of boxes) { | ||
const t = []; | ||
let m = 0; | ||
for (const j of i) { | ||
if (!ind.get(j)) | ||
continue; | ||
const x = l[ind.get(j)]; | ||
t.push(x.text); | ||
m += x.mean; | ||
result.sort(function (a, b) { | ||
return a[0][0][1] - b[0][0][1]; | ||
}); | ||
return result; | ||
} | ||
var boxes = groupBoxesByMidlineDifference((0, _toConsumableArray2["default"])(ind.keys())); | ||
var _iterator5 = _createForOfIteratorHelper(boxes), | ||
_step5; | ||
try { | ||
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { | ||
var _i2 = _step5.value; | ||
var t = []; | ||
var m = 0; | ||
var _iterator6 = _createForOfIteratorHelper(_i2), | ||
_step6; | ||
try { | ||
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { | ||
var j = _step6.value; | ||
if (!ind.get(j)) continue; | ||
var x = l[ind.get(j)]; | ||
t.push(x.text); | ||
m += x.mean; | ||
} | ||
let box = undefined; | ||
if (i.at(0) && i.at(-1)) { | ||
box = [i.at(0)[0], i.at(-1)[1], i.at(-1)[2], i.at(0)[3]]; | ||
} | ||
line.push({ | ||
mean: m / i.length, | ||
text: t.join(' '), | ||
box: box, | ||
}); | ||
} catch (err) { | ||
_iterator6.e(err); | ||
} finally { | ||
_iterator6.f(); | ||
} | ||
var box = undefined; | ||
if (_i2.at(0) && _i2.at(-1)) { | ||
box = [_i2.at(0)[0], _i2.at(-1)[1], _i2.at(-1)[2], _i2.at(0)[3]]; | ||
} | ||
line.push({ | ||
mean: m / _i2.length, | ||
text: t.join(' '), | ||
box: box | ||
}); | ||
} | ||
return line; | ||
} | ||
//# sourceMappingURL=Recognition.js.map | ||
} catch (err) { | ||
_iterator5.e(err); | ||
} finally { | ||
_iterator5.f(); | ||
} | ||
return line; | ||
} |
119
build/Ocr.js
@@ -1,20 +0,99 @@ | ||
import { Detection, Recognition } from './models/index.js'; | ||
export class Ocr { | ||
static async create(options = {}) { | ||
const detection = await Detection.create(options); | ||
const recognition = await Recognition.create(options); | ||
return new Ocr({ detection, recognition }); | ||
} | ||
#detection; | ||
#recognition; | ||
constructor({ detection, recognition, }) { | ||
this.#detection = detection; | ||
this.#recognition = recognition; | ||
} | ||
async detect(image, options = {}) { | ||
const lineImages = await this.#detection.run(image, options.detectionOptions ?? {}); | ||
const texts = await this.#recognition.run(lineImages, options.recognitionOptions ?? {}); | ||
return texts; | ||
} | ||
} | ||
//# sourceMappingURL=Ocr.js.map | ||
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.Ocr = void 0; | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); | ||
var _index = require("./models/index.js"); | ||
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } | ||
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } | ||
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } | ||
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } | ||
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } | ||
var _detection = /*#__PURE__*/new WeakMap(); | ||
var _recognition = /*#__PURE__*/new WeakMap(); | ||
var Ocr = exports.Ocr = /*#__PURE__*/function () { | ||
function Ocr(_ref) { | ||
var detection = _ref.detection, | ||
recognition = _ref.recognition; | ||
(0, _classCallCheck2["default"])(this, Ocr); | ||
_classPrivateFieldInitSpec(this, _detection, void 0); | ||
_classPrivateFieldInitSpec(this, _recognition, void 0); | ||
_classPrivateFieldSet(_detection, this, detection); | ||
_classPrivateFieldSet(_recognition, this, recognition); | ||
} | ||
return (0, _createClass2["default"])(Ocr, [{ | ||
key: "detect", | ||
value: function () { | ||
var _detect = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(image) { | ||
var _options$detectionOpt, _options$recognitionO; | ||
var options, | ||
lineImages, | ||
texts, | ||
_args = arguments; | ||
return _regenerator["default"].wrap(function _callee$(_context) { | ||
while (1) switch (_context.prev = _context.next) { | ||
case 0: | ||
options = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}; | ||
_context.next = 3; | ||
return _classPrivateFieldGet(_detection, this).run(image, (_options$detectionOpt = options.detectionOptions) !== null && _options$detectionOpt !== void 0 ? _options$detectionOpt : {}); | ||
case 3: | ||
lineImages = _context.sent; | ||
_context.next = 6; | ||
return _classPrivateFieldGet(_recognition, this).run(lineImages, (_options$recognitionO = options.recognitionOptions) !== null && _options$recognitionO !== void 0 ? _options$recognitionO : {}); | ||
case 6: | ||
texts = _context.sent; | ||
return _context.abrupt("return", texts); | ||
case 8: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
}, _callee, this); | ||
})); | ||
function detect(_x) { | ||
return _detect.apply(this, arguments); | ||
} | ||
return detect; | ||
}() | ||
}], [{ | ||
key: "create", | ||
value: function () { | ||
var _create = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2() { | ||
var options, | ||
detection, | ||
recognition, | ||
_args2 = arguments; | ||
return _regenerator["default"].wrap(function _callee2$(_context2) { | ||
while (1) switch (_context2.prev = _context2.next) { | ||
case 0: | ||
options = _args2.length > 0 && _args2[0] !== undefined ? _args2[0] : {}; | ||
_context2.next = 3; | ||
return _index.Detection.create(options); | ||
case 3: | ||
detection = _context2.sent; | ||
_context2.next = 6; | ||
return _index.Recognition.create(options); | ||
case 6: | ||
recognition = _context2.sent; | ||
return _context2.abrupt("return", new Ocr({ | ||
detection: detection, | ||
recognition: recognition | ||
})); | ||
case 8: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
}, _callee2); | ||
})); | ||
function create() { | ||
return _create.apply(this, arguments); | ||
} | ||
return create; | ||
}() | ||
}]); | ||
}(); |
@@ -1,2 +0,16 @@ | ||
export * from './types.js'; | ||
//# sourceMappingURL=index.js.map | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _types = require("./types.js"); | ||
Object.keys(_types).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _types[key]) return; | ||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: function get() { | ||
return _types[key]; | ||
} | ||
}); | ||
}); |
@@ -1,5 +0,26 @@ | ||
import { InferenceSession } from 'onnxruntime-common'; | ||
import { ImageRawBase as ImageRaw } from '../backend/ImageRawBase.js'; | ||
export { FileUtilsBase as FileUtils } from '../backend/FileUtilsBase.js'; | ||
export { ImageRaw, InferenceSession }; | ||
//# sourceMappingURL=types.js.map | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
Object.defineProperty(exports, "FileUtils", { | ||
enumerable: true, | ||
get: function get() { | ||
return _FileUtilsBase.FileUtilsBase; | ||
} | ||
}); | ||
Object.defineProperty(exports, "ImageRaw", { | ||
enumerable: true, | ||
get: function get() { | ||
return _ImageRawBase.ImageRawBase; | ||
} | ||
}); | ||
Object.defineProperty(exports, "InferenceSession", { | ||
enumerable: true, | ||
get: function get() { | ||
return _onnxruntimeCommon.InferenceSession; | ||
} | ||
}); | ||
var _onnxruntimeCommon = require("onnxruntime-common"); | ||
var _ImageRawBase = require("../backend/ImageRawBase.js"); | ||
var _FileUtilsBase = require("../backend/FileUtilsBase.js"); |
{ | ||
"name": "@tzmax/ocr-common", | ||
"description": "Guten OCR is a high accurate text detection (OCR) Javascript/Typescript library that runs on Node.js, Browser, React Native and C++. Based on PaddleOCR and ONNX runtime", | ||
"version": "1.4.1", | ||
"type": "module", | ||
"version": "1.4.6", | ||
"type": "commonjs", | ||
"license": "MIT", | ||
@@ -27,3 +27,4 @@ "repository": { | ||
"scripts": { | ||
"prepublishOnly": "cp ../../README.md . && ./ake build", | ||
"build:cjs": "babel --config-file ../../babel.config.cjs ./build --out-dir ./build", | ||
"prepublishOnly": "cp ../../README.md . && ./ake build && npm run build:cjs", | ||
"postpublish": "rm README.md && rm -rf build" | ||
@@ -30,0 +31,0 @@ }, |
@@ -83,3 +83,10 @@ # Guten OCR | ||
onnxOptions?: {} // Node only. Pass to ONNX Runtime | ||
}): Promise<Result> | ||
}): Promise<TextLine[]> | ||
TextLine { | ||
text: string | ||
score: number | ||
frame: { top, left, width, height } | ||
} | ||
``` | ||
@@ -86,0 +93,0 @@ |
@@ -7,3 +7,3 @@ import type { | ||
SplitIntoLineImages as SplitIntoLineImagesType, | ||
} from '#common/types' | ||
} from '../types' | ||
@@ -18,4 +18,4 @@ let FileUtils: FileUtilsType | any = undefined as unknown as FileUtilsType | ||
FileUtils: FileUtilsType | ||
ImageRaw: ImageRawType | ||
InferenceSession: InferenceSessionType | ||
ImageRaw: ImageRawType | any | ||
InferenceSession: InferenceSessionType | any | ||
splitIntoLineImages: SplitIntoLineImagesType | ||
@@ -22,0 +22,0 @@ defaultModels: ModelCreateOptionsType['models'] |
@@ -1,3 +0,3 @@ | ||
import cv from '@techstark/opencv-js' | ||
import clipper from 'js-clipper' | ||
import * as cv from '@techstark/opencv-js' | ||
import * as clipper from 'js-clipper' | ||
import { ImageRaw } from '#common/backend' | ||
@@ -122,4 +122,4 @@ import type { LineImage, ImageRaw as ImageRawType } from '#common/types' | ||
}) | ||
const offset = new clipper.ClipperOffset() | ||
offset.AddPath(tmpArr, clipper.JoinType.jtRound, clipper.EndType.etClosedPolygon) | ||
const offset = new (clipper as any).ClipperOffset() | ||
offset.AddPath(tmpArr, (clipper as any).JoinType.jtRound, (clipper as any).EndType.etClosedPolygon) | ||
const expanded: { X: number; Y: number }[][] = [] | ||
@@ -126,0 +126,0 @@ offset.Execute(expanded, distance) |
@@ -36,5 +36,5 @@ import type { InferenceSession as InferenceSessionCommon, Tensor } from 'onnxruntime-common' | ||
// Run the model | ||
console.time('Detection') | ||
// console.time('Detection') | ||
const modelOutput = await this.runModel({ modelData, onnxOptions }) | ||
console.timeEnd('Detection') | ||
// console.timeEnd('Detection') | ||
@@ -41,0 +41,0 @@ // Convert output data back to image data |
@@ -49,3 +49,3 @@ import type { InferenceSession as InferenceSessionCommon, Tensor } from 'onnxruntime-common' | ||
const allLines: Line[] = [] | ||
console.time('Recognition') | ||
// console.time('Recognition') | ||
for (const modelData of modelDatas) { | ||
@@ -52,0 +52,0 @@ // Run model for each line image |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
119447
2257
112
No