mirror of
https://github.com/serratus/quaggaJS.git
synced 2026-08-12 21:21:42 +08:00
Moved files into meaningful folders
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
export default {
|
||||
init: function(arr, val) {
|
||||
var l = arr.length;
|
||||
while (l--) {
|
||||
arr[l] = val;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shuffles the content of an array
|
||||
* @return {Array} the array itself shuffled
|
||||
*/
|
||||
shuffle: function(arr) {
|
||||
var i = arr.length - 1, j, x;
|
||||
for (i; i >= 0; i--) {
|
||||
j = Math.floor(Math.random() * i);
|
||||
x = arr[i];
|
||||
arr[i] = arr[j];
|
||||
arr[j] = x;
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
||||
toPointList: function(arr) {
|
||||
var i, j, row = [], rows = [];
|
||||
for ( i = 0; i < arr.length; i++) {
|
||||
row = [];
|
||||
for ( j = 0; j < arr[i].length; j++) {
|
||||
row[j] = arr[i][j];
|
||||
}
|
||||
rows[i] = "[" + row.join(",") + "]";
|
||||
}
|
||||
return "[" + rows.join(",\r\n") + "]";
|
||||
},
|
||||
|
||||
/**
|
||||
* returns the elements which's score is bigger than the threshold
|
||||
* @return {Array} the reduced array
|
||||
*/
|
||||
threshold: function(arr, threshold, scoreFunc) {
|
||||
var i, queue = [];
|
||||
for ( i = 0; i < arr.length; i++) {
|
||||
if (scoreFunc.apply(arr, [arr[i]]) >= threshold) {
|
||||
queue.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return queue;
|
||||
},
|
||||
|
||||
maxIndex: function(arr) {
|
||||
var i, max = 0;
|
||||
for ( i = 0; i < arr.length; i++) {
|
||||
if (arr[i] > arr[max]) {
|
||||
max = i;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
},
|
||||
|
||||
max: function(arr) {
|
||||
var i, max = 0;
|
||||
for ( i = 0; i < arr.length; i++) {
|
||||
if (arr[i] > max) {
|
||||
max = arr[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
},
|
||||
|
||||
sum: function(arr) {
|
||||
var length = arr.length,
|
||||
sum = 0;
|
||||
|
||||
while (length--) {
|
||||
sum += arr[length];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import {vec2} from 'gl-matrix';
|
||||
/**
|
||||
* Creates a cluster for grouping similar orientations of datapoints
|
||||
*/
|
||||
export default {
|
||||
create: function(point, threshold) {
|
||||
var points = [],
|
||||
center = {
|
||||
rad: 0,
|
||||
vec: vec2.clone([0, 0])
|
||||
},
|
||||
pointMap = {};
|
||||
|
||||
function init() {
|
||||
add(point);
|
||||
updateCenter();
|
||||
}
|
||||
|
||||
function add(pointToAdd) {
|
||||
pointMap[pointToAdd.id] = pointToAdd;
|
||||
points.push(pointToAdd);
|
||||
}
|
||||
|
||||
function updateCenter() {
|
||||
var i, sum = 0;
|
||||
for ( i = 0; i < points.length; i++) {
|
||||
sum += points[i].rad;
|
||||
}
|
||||
center.rad = sum / points.length;
|
||||
center.vec = vec2.clone([Math.cos(center.rad), Math.sin(center.rad)]);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
add: function(pointToAdd) {
|
||||
if (!pointMap[pointToAdd.id]) {
|
||||
add(pointToAdd);
|
||||
updateCenter();
|
||||
}
|
||||
},
|
||||
fits: function(otherPoint) {
|
||||
// check cosine similarity to center-angle
|
||||
var similarity = Math.abs(vec2.dot(otherPoint.point.vec, center.vec));
|
||||
if (similarity > threshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getPoints: function() {
|
||||
return points;
|
||||
},
|
||||
getCenter: function() {
|
||||
return center;
|
||||
}
|
||||
};
|
||||
},
|
||||
createPoint: function(newPoint, id, property) {
|
||||
return {
|
||||
rad: newPoint[property],
|
||||
point: newPoint,
|
||||
id: id
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,750 @@
|
||||
import Cluster2 from './cluster';
|
||||
import ArrayHelper from './array_helper';
|
||||
import {vec2, vec3} from 'gl-matrix';
|
||||
|
||||
var CVUtils = {};
|
||||
|
||||
/**
|
||||
* @param x x-coordinate
|
||||
* @param y y-coordinate
|
||||
* @return ImageReference {x,y} Coordinate
|
||||
*/
|
||||
CVUtils.imageRef = function(x, y) {
|
||||
var that = {
|
||||
x: x,
|
||||
y: y,
|
||||
toVec2: function() {
|
||||
return vec2.clone([this.x, this.y]);
|
||||
},
|
||||
toVec3: function() {
|
||||
return vec3.clone([this.x, this.y, 1]);
|
||||
},
|
||||
round: function() {
|
||||
this.x = this.x > 0.0 ? Math.floor(this.x + 0.5) : Math.floor(this.x - 0.5);
|
||||
this.y = this.y > 0.0 ? Math.floor(this.y + 0.5) : Math.floor(this.y - 0.5);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
return that;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes an integral image of a given grayscale image.
|
||||
* @param imageDataContainer {ImageDataContainer} the image to be integrated
|
||||
*/
|
||||
CVUtils.computeIntegralImage2 = function(imageWrapper, integralWrapper) {
|
||||
var imageData = imageWrapper.data;
|
||||
var width = imageWrapper.size.x;
|
||||
var height = imageWrapper.size.y;
|
||||
var integralImageData = integralWrapper.data;
|
||||
var sum = 0, posA = 0, posB = 0, posC = 0, posD = 0, x, y;
|
||||
|
||||
// sum up first column
|
||||
posB = width;
|
||||
sum = 0;
|
||||
for ( y = 1; y < height; y++) {
|
||||
sum += imageData[posA];
|
||||
integralImageData[posB] += sum;
|
||||
posA += width;
|
||||
posB += width;
|
||||
}
|
||||
|
||||
posA = 0;
|
||||
posB = 1;
|
||||
sum = 0;
|
||||
for ( x = 1; x < width; x++) {
|
||||
sum += imageData[posA];
|
||||
integralImageData[posB] += sum;
|
||||
posA++;
|
||||
posB++;
|
||||
}
|
||||
|
||||
for ( y = 1; y < height; y++) {
|
||||
posA = y * width + 1;
|
||||
posB = (y - 1) * width + 1;
|
||||
posC = y * width;
|
||||
posD = (y - 1) * width;
|
||||
for ( x = 1; x < width; x++) {
|
||||
integralImageData[posA] +=
|
||||
imageData[posA] + integralImageData[posB] + integralImageData[posC] - integralImageData[posD];
|
||||
posA++;
|
||||
posB++;
|
||||
posC++;
|
||||
posD++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.computeIntegralImage = function(imageWrapper, integralWrapper) {
|
||||
var imageData = imageWrapper.data;
|
||||
var width = imageWrapper.size.x;
|
||||
var height = imageWrapper.size.y;
|
||||
var integralImageData = integralWrapper.data;
|
||||
var sum = 0;
|
||||
|
||||
// sum up first row
|
||||
for (var i = 0; i < width; i++) {
|
||||
sum += imageData[i];
|
||||
integralImageData[i] = sum;
|
||||
}
|
||||
|
||||
for (var v = 1; v < height; v++) {
|
||||
sum = 0;
|
||||
for (var u = 0; u < width; u++) {
|
||||
sum += imageData[v * width + u];
|
||||
integralImageData[((v) * width) + u] = sum + integralImageData[(v - 1) * width + u];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.thresholdImage = function(imageWrapper, threshold, targetWrapper) {
|
||||
if (!targetWrapper) {
|
||||
targetWrapper = imageWrapper;
|
||||
}
|
||||
var imageData = imageWrapper.data, length = imageData.length, targetData = targetWrapper.data;
|
||||
|
||||
while (length--) {
|
||||
targetData[length] = imageData[length] < threshold ? 1 : 0;
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.computeHistogram = function(imageWrapper, bitsPerPixel) {
|
||||
if (!bitsPerPixel) {
|
||||
bitsPerPixel = 8;
|
||||
}
|
||||
var imageData = imageWrapper.data,
|
||||
length = imageData.length,
|
||||
bitShift = 8 - bitsPerPixel,
|
||||
bucketCnt = 1 << bitsPerPixel,
|
||||
hist = new Int32Array(bucketCnt);
|
||||
|
||||
while (length--) {
|
||||
hist[imageData[length] >> bitShift]++;
|
||||
}
|
||||
return hist;
|
||||
};
|
||||
|
||||
CVUtils.sharpenLine = function(line) {
|
||||
var i,
|
||||
length = line.length,
|
||||
left = line[0],
|
||||
center = line[1],
|
||||
right;
|
||||
|
||||
for (i = 1; i < length - 1; i++) {
|
||||
right = line[i + 1];
|
||||
// -1 4 -1 kernel
|
||||
line[i - 1] = (((center * 2) - left - right)) & 255;
|
||||
left = center;
|
||||
center = right;
|
||||
}
|
||||
return line;
|
||||
};
|
||||
|
||||
CVUtils.determineOtsuThreshold = function(imageWrapper, bitsPerPixel) {
|
||||
if (!bitsPerPixel) {
|
||||
bitsPerPixel = 8;
|
||||
}
|
||||
var hist,
|
||||
threshold,
|
||||
bitShift = 8 - bitsPerPixel;
|
||||
|
||||
function px(init, end) {
|
||||
var sum = 0, i;
|
||||
for ( i = init; i <= end; i++) {
|
||||
sum += hist[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
function mx(init, end) {
|
||||
var i, sum = 0;
|
||||
|
||||
for ( i = init; i <= end; i++) {
|
||||
sum += i * hist[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
function determineThreshold() {
|
||||
var vet = [0], p1, p2, p12, k, m1, m2, m12,
|
||||
max = (1 << bitsPerPixel) - 1;
|
||||
|
||||
hist = CVUtils.computeHistogram(imageWrapper, bitsPerPixel);
|
||||
for ( k = 1; k < max; k++) {
|
||||
p1 = px(0, k);
|
||||
p2 = px(k + 1, max);
|
||||
p12 = p1 * p2;
|
||||
if (p12 === 0) {
|
||||
p12 = 1;
|
||||
}
|
||||
m1 = mx(0, k) * p2;
|
||||
m2 = mx(k + 1, max) * p1;
|
||||
m12 = m1 - m2;
|
||||
vet[k] = m12 * m12 / p12;
|
||||
}
|
||||
return ArrayHelper.maxIndex(vet);
|
||||
}
|
||||
|
||||
threshold = determineThreshold();
|
||||
return threshold << bitShift;
|
||||
};
|
||||
|
||||
CVUtils.otsuThreshold = function(imageWrapper, targetWrapper) {
|
||||
var threshold = CVUtils.determineOtsuThreshold(imageWrapper);
|
||||
|
||||
CVUtils.thresholdImage(imageWrapper, threshold, targetWrapper);
|
||||
return threshold;
|
||||
};
|
||||
|
||||
// local thresholding
|
||||
CVUtils.computeBinaryImage = function(imageWrapper, integralWrapper, targetWrapper) {
|
||||
CVUtils.computeIntegralImage(imageWrapper, integralWrapper);
|
||||
|
||||
if (!targetWrapper) {
|
||||
targetWrapper = imageWrapper;
|
||||
}
|
||||
var imageData = imageWrapper.data;
|
||||
var targetData = targetWrapper.data;
|
||||
var width = imageWrapper.size.x;
|
||||
var height = imageWrapper.size.y;
|
||||
var integralImageData = integralWrapper.data;
|
||||
var sum = 0, v, u, kernel = 3, A, B, C, D, avg, size = (kernel * 2 + 1) * (kernel * 2 + 1);
|
||||
|
||||
// clear out top & bottom-border
|
||||
for ( v = 0; v <= kernel; v++) {
|
||||
for ( u = 0; u < width; u++) {
|
||||
targetData[((v) * width) + u] = 0;
|
||||
targetData[(((height - 1) - v) * width) + u] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// clear out left & right border
|
||||
for ( v = kernel; v < height - kernel; v++) {
|
||||
for ( u = 0; u <= kernel; u++) {
|
||||
targetData[((v) * width) + u] = 0;
|
||||
targetData[((v) * width) + (width - 1 - u)] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for ( v = kernel + 1; v < height - kernel - 1; v++) {
|
||||
for ( u = kernel + 1; u < width - kernel; u++) {
|
||||
A = integralImageData[(v - kernel - 1) * width + (u - kernel - 1)];
|
||||
B = integralImageData[(v - kernel - 1) * width + (u + kernel)];
|
||||
C = integralImageData[(v + kernel) * width + (u - kernel - 1)];
|
||||
D = integralImageData[(v + kernel) * width + (u + kernel)];
|
||||
sum = D - C - B + A;
|
||||
avg = sum / (size);
|
||||
targetData[v * width + u] = imageData[v * width + u] > (avg + 5) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.cluster = function(points, threshold, property) {
|
||||
var i, k, cluster, point, clusters = [];
|
||||
|
||||
if (!property) {
|
||||
property = "rad";
|
||||
}
|
||||
|
||||
function addToCluster(newPoint) {
|
||||
var found = false;
|
||||
for ( k = 0; k < clusters.length; k++) {
|
||||
cluster = clusters[k];
|
||||
if (cluster.fits(newPoint)) {
|
||||
cluster.add(newPoint);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// iterate over each cloud
|
||||
for ( i = 0; i < points.length; i++) {
|
||||
point = Cluster2.createPoint(points[i], i, property);
|
||||
if (!addToCluster(point)) {
|
||||
clusters.push(Cluster2.create(point, threshold));
|
||||
}
|
||||
}
|
||||
return clusters;
|
||||
};
|
||||
|
||||
CVUtils.Tracer = {
|
||||
trace: function(points, vec) {
|
||||
var iteration, maxIterations = 10, top = [], result = [], centerPos = 0, currentPos = 0;
|
||||
|
||||
function trace(idx, forward) {
|
||||
var from, to, toIdx, predictedPos, thresholdX = 1, thresholdY = Math.abs(vec[1] / 10), found = false;
|
||||
|
||||
function match(pos, predicted) {
|
||||
if (pos.x > (predicted.x - thresholdX)
|
||||
&& pos.x < (predicted.x + thresholdX)
|
||||
&& pos.y > (predicted.y - thresholdY)
|
||||
&& pos.y < (predicted.y + thresholdY)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the next index is within the vec specifications
|
||||
// if not, check as long as the threshold is met
|
||||
|
||||
from = points[idx];
|
||||
if (forward) {
|
||||
predictedPos = {
|
||||
x: from.x + vec[0],
|
||||
y: from.y + vec[1]
|
||||
};
|
||||
} else {
|
||||
predictedPos = {
|
||||
x: from.x - vec[0],
|
||||
y: from.y - vec[1]
|
||||
};
|
||||
}
|
||||
|
||||
toIdx = forward ? idx + 1 : idx - 1;
|
||||
to = points[toIdx];
|
||||
while (to && ( found = match(to, predictedPos)) !== true && (Math.abs(to.y - from.y) < vec[1])) {
|
||||
toIdx = forward ? toIdx + 1 : toIdx - 1;
|
||||
to = points[toIdx];
|
||||
}
|
||||
|
||||
return found ? toIdx : null;
|
||||
}
|
||||
|
||||
for ( iteration = 0; iteration < maxIterations; iteration++) {
|
||||
// randomly select point to start with
|
||||
centerPos = Math.floor(Math.random() * points.length);
|
||||
|
||||
// trace forward
|
||||
top = [];
|
||||
currentPos = centerPos;
|
||||
top.push(points[currentPos]);
|
||||
while (( currentPos = trace(currentPos, true)) !== null) {
|
||||
top.push(points[currentPos]);
|
||||
}
|
||||
if (centerPos > 0) {
|
||||
currentPos = centerPos;
|
||||
while (( currentPos = trace(currentPos, false)) !== null) {
|
||||
top.push(points[currentPos]);
|
||||
}
|
||||
}
|
||||
|
||||
if (top.length > result.length) {
|
||||
result = top;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.DILATE = 1;
|
||||
CVUtils.ERODE = 2;
|
||||
|
||||
CVUtils.dilate = function(inImageWrapper, outImageWrapper) {
|
||||
var v,
|
||||
u,
|
||||
inImageData = inImageWrapper.data,
|
||||
outImageData = outImageWrapper.data,
|
||||
height = inImageWrapper.size.y,
|
||||
width = inImageWrapper.size.x,
|
||||
sum,
|
||||
yStart1,
|
||||
yStart2,
|
||||
xStart1,
|
||||
xStart2;
|
||||
|
||||
for ( v = 1; v < height - 1; v++) {
|
||||
for ( u = 1; u < width - 1; u++) {
|
||||
yStart1 = v - 1;
|
||||
yStart2 = v + 1;
|
||||
xStart1 = u - 1;
|
||||
xStart2 = u + 1;
|
||||
sum = inImageData[yStart1 * width + xStart1] + inImageData[yStart1 * width + xStart2] +
|
||||
inImageData[v * width + u] +
|
||||
inImageData[yStart2 * width + xStart1] + inImageData[yStart2 * width + xStart2];
|
||||
outImageData[v * width + u] = sum > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.erode = function(inImageWrapper, outImageWrapper) {
|
||||
var v,
|
||||
u,
|
||||
inImageData = inImageWrapper.data,
|
||||
outImageData = outImageWrapper.data,
|
||||
height = inImageWrapper.size.y,
|
||||
width = inImageWrapper.size.x,
|
||||
sum,
|
||||
yStart1,
|
||||
yStart2,
|
||||
xStart1,
|
||||
xStart2;
|
||||
|
||||
for ( v = 1; v < height - 1; v++) {
|
||||
for ( u = 1; u < width - 1; u++) {
|
||||
yStart1 = v - 1;
|
||||
yStart2 = v + 1;
|
||||
xStart1 = u - 1;
|
||||
xStart2 = u + 1;
|
||||
sum = inImageData[yStart1 * width + xStart1] + inImageData[yStart1 * width + xStart2] +
|
||||
inImageData[v * width + u] +
|
||||
inImageData[yStart2 * width + xStart1] + inImageData[yStart2 * width + xStart2];
|
||||
outImageData[v * width + u] = sum === 5 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.subtract = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
|
||||
if (!resultImageWrapper) {
|
||||
resultImageWrapper = aImageWrapper;
|
||||
}
|
||||
var length = aImageWrapper.data.length,
|
||||
aImageData = aImageWrapper.data,
|
||||
bImageData = bImageWrapper.data,
|
||||
cImageData = resultImageWrapper.data;
|
||||
|
||||
while (length--) {
|
||||
cImageData[length] = aImageData[length] - bImageData[length];
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.bitwiseOr = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
|
||||
if (!resultImageWrapper) {
|
||||
resultImageWrapper = aImageWrapper;
|
||||
}
|
||||
var length = aImageWrapper.data.length,
|
||||
aImageData = aImageWrapper.data,
|
||||
bImageData = bImageWrapper.data,
|
||||
cImageData = resultImageWrapper.data;
|
||||
|
||||
while (length--) {
|
||||
cImageData[length] = aImageData[length] || bImageData[length];
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.countNonZero = function(imageWrapper) {
|
||||
var length = imageWrapper.data.length, data = imageWrapper.data, sum = 0;
|
||||
|
||||
while (length--) {
|
||||
sum += data[length];
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
CVUtils.topGeneric = function(list, top, scoreFunc) {
|
||||
var i, minIdx = 0, min = 0, queue = [], score, hit, pos;
|
||||
|
||||
for ( i = 0; i < top; i++) {
|
||||
queue[i] = {
|
||||
score: 0,
|
||||
item: null
|
||||
};
|
||||
}
|
||||
|
||||
for ( i = 0; i < list.length; i++) {
|
||||
score = scoreFunc.apply(this, [list[i]]);
|
||||
if (score > min) {
|
||||
hit = queue[minIdx];
|
||||
hit.score = score;
|
||||
hit.item = list[i];
|
||||
min = Number.MAX_VALUE;
|
||||
for ( pos = 0; pos < top; pos++) {
|
||||
if (queue[pos].score < min) {
|
||||
min = queue[pos].score;
|
||||
minIdx = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queue;
|
||||
};
|
||||
|
||||
CVUtils.grayArrayFromImage = function(htmlImage, offsetX, ctx, array) {
|
||||
ctx.drawImage(htmlImage, offsetX, 0, htmlImage.width, htmlImage.height);
|
||||
var ctxData = ctx.getImageData(offsetX, 0, htmlImage.width, htmlImage.height).data;
|
||||
CVUtils.computeGray(ctxData, array);
|
||||
};
|
||||
|
||||
CVUtils.grayArrayFromContext = function(ctx, size, offset, array) {
|
||||
var ctxData = ctx.getImageData(offset.x, offset.y, size.x, size.y).data;
|
||||
CVUtils.computeGray(ctxData, array);
|
||||
};
|
||||
|
||||
CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) {
|
||||
var topRowIdx = 0;
|
||||
var bottomRowIdx = size.x;
|
||||
var endIdx = Math.floor(canvasData.length / 4);
|
||||
var outWidth = size.x / 2;
|
||||
var outImgIdx = 0;
|
||||
var inWidth = size.x;
|
||||
var i;
|
||||
|
||||
while (bottomRowIdx < endIdx) {
|
||||
for ( i = 0; i < outWidth; i++) {
|
||||
outArray[outImgIdx] = Math.floor((
|
||||
(0.299 * canvasData[topRowIdx * 4 + 0] +
|
||||
0.587 * canvasData[topRowIdx * 4 + 1] +
|
||||
0.114 * canvasData[topRowIdx * 4 + 2]) +
|
||||
(0.299 * canvasData[(topRowIdx + 1) * 4 + 0] +
|
||||
0.587 * canvasData[(topRowIdx + 1) * 4 + 1] +
|
||||
0.114 * canvasData[(topRowIdx + 1) * 4 + 2]) +
|
||||
(0.299 * canvasData[(bottomRowIdx) * 4 + 0] +
|
||||
0.587 * canvasData[(bottomRowIdx) * 4 + 1] +
|
||||
0.114 * canvasData[(bottomRowIdx) * 4 + 2]) +
|
||||
(0.299 * canvasData[(bottomRowIdx + 1) * 4 + 0] +
|
||||
0.587 * canvasData[(bottomRowIdx + 1) * 4 + 1] +
|
||||
0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4);
|
||||
outImgIdx++;
|
||||
topRowIdx = topRowIdx + 2;
|
||||
bottomRowIdx = bottomRowIdx + 2;
|
||||
}
|
||||
topRowIdx = topRowIdx + inWidth;
|
||||
bottomRowIdx = bottomRowIdx + inWidth;
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.computeGray = function(imageData, outArray, config) {
|
||||
var l = (imageData.length / 4) | 0,
|
||||
i,
|
||||
singleChannel = config && config.singleChannel === true;
|
||||
|
||||
if (singleChannel) {
|
||||
for (i = 0; i < l; i++) {
|
||||
outArray[i] = imageData[i * 4 + 0];
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < l; i++) {
|
||||
outArray[i] = Math.floor(
|
||||
0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.loadImageArray = function(src, callback, canvas) {
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
}
|
||||
var img = new Image();
|
||||
img.callback = callback;
|
||||
img.onload = function() {
|
||||
canvas.width = this.width;
|
||||
canvas.height = this.height;
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(this, 0, 0);
|
||||
var array = new Uint8Array(this.width * this.height);
|
||||
ctx.drawImage(this, 0, 0);
|
||||
var data = ctx.getImageData(0, 0, this.width, this.height).data;
|
||||
CVUtils.computeGray(data, array);
|
||||
this.callback(array, {
|
||||
x: this.width,
|
||||
y: this.height
|
||||
}, this);
|
||||
};
|
||||
img.src = src;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param inImg {ImageWrapper} input image to be sampled
|
||||
* @param outImg {ImageWrapper} to be stored in
|
||||
*/
|
||||
CVUtils.halfSample = function(inImgWrapper, outImgWrapper) {
|
||||
var inImg = inImgWrapper.data;
|
||||
var inWidth = inImgWrapper.size.x;
|
||||
var outImg = outImgWrapper.data;
|
||||
var topRowIdx = 0;
|
||||
var bottomRowIdx = inWidth;
|
||||
var endIdx = inImg.length;
|
||||
var outWidth = inWidth / 2;
|
||||
var outImgIdx = 0;
|
||||
while (bottomRowIdx < endIdx) {
|
||||
for (var i = 0; i < outWidth; i++) {
|
||||
outImg[outImgIdx] = Math.floor(
|
||||
(inImg[topRowIdx] + inImg[topRowIdx + 1] + inImg[bottomRowIdx] + inImg[bottomRowIdx + 1]) / 4);
|
||||
outImgIdx++;
|
||||
topRowIdx = topRowIdx + 2;
|
||||
bottomRowIdx = bottomRowIdx + 2;
|
||||
}
|
||||
topRowIdx = topRowIdx + inWidth;
|
||||
bottomRowIdx = bottomRowIdx + inWidth;
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.hsv2rgb = function(hsv, rgb) {
|
||||
var h = hsv[0],
|
||||
s = hsv[1],
|
||||
v = hsv[2],
|
||||
c = v * s,
|
||||
x = c * (1 - Math.abs((h / 60) % 2 - 1)),
|
||||
m = v - c,
|
||||
r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
rgb = rgb || [0, 0, 0];
|
||||
|
||||
if (h < 60) {
|
||||
r = c;
|
||||
g = x;
|
||||
} else if (h < 120) {
|
||||
r = x;
|
||||
g = c;
|
||||
} else if (h < 180) {
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h < 240) {
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h < 300) {
|
||||
r = x;
|
||||
b = c;
|
||||
} else if (h < 360) {
|
||||
r = c;
|
||||
b = x;
|
||||
}
|
||||
rgb[0] = ((r + m) * 255) | 0;
|
||||
rgb[1] = ((g + m) * 255) | 0;
|
||||
rgb[2] = ((b + m) * 255) | 0;
|
||||
return rgb;
|
||||
};
|
||||
|
||||
CVUtils._computeDivisors = function(n) {
|
||||
var largeDivisors = [],
|
||||
divisors = [],
|
||||
i;
|
||||
|
||||
for (i = 1; i < Math.sqrt(n) + 1; i++) {
|
||||
if (n % i === 0) {
|
||||
divisors.push(i);
|
||||
if (i !== n / i) {
|
||||
largeDivisors.unshift(Math.floor(n / i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return divisors.concat(largeDivisors);
|
||||
};
|
||||
|
||||
CVUtils._computeIntersection = function(arr1, arr2) {
|
||||
var i = 0,
|
||||
j = 0,
|
||||
result = [];
|
||||
|
||||
while (i < arr1.length && j < arr2.length) {
|
||||
if (arr1[i] === arr2[j]) {
|
||||
result.push(arr1[i]);
|
||||
i++;
|
||||
j++;
|
||||
} else if (arr1[i] > arr2[j]) {
|
||||
j++;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
CVUtils.calculatePatchSize = function(patchSize, imgSize) {
|
||||
var divisorsX = this._computeDivisors(imgSize.x),
|
||||
divisorsY = this._computeDivisors(imgSize.y),
|
||||
wideSide = Math.max(imgSize.x, imgSize.y),
|
||||
common = this._computeIntersection(divisorsX, divisorsY),
|
||||
nrOfPatchesList = [8, 10, 15, 20, 32, 60, 80],
|
||||
nrOfPatchesMap = {
|
||||
"x-small": 5,
|
||||
"small": 4,
|
||||
"medium": 3,
|
||||
"large": 2,
|
||||
"x-large": 1
|
||||
},
|
||||
nrOfPatchesIdx = nrOfPatchesMap[patchSize] || nrOfPatchesMap.medium,
|
||||
nrOfPatches = nrOfPatchesList[nrOfPatchesIdx],
|
||||
desiredPatchSize = Math.floor(wideSide / nrOfPatches),
|
||||
optimalPatchSize;
|
||||
|
||||
function findPatchSizeForDivisors(divisors) {
|
||||
var i = 0,
|
||||
found = divisors[Math.floor(divisors.length / 2)];
|
||||
|
||||
while (i < (divisors.length - 1) && divisors[i] < desiredPatchSize) {
|
||||
i++;
|
||||
}
|
||||
if (i > 0) {
|
||||
if (Math.abs(divisors[i] - desiredPatchSize) > Math.abs(divisors[i - 1] - desiredPatchSize)) {
|
||||
found = divisors[i - 1];
|
||||
} else {
|
||||
found = divisors[i];
|
||||
}
|
||||
}
|
||||
if (desiredPatchSize / found < nrOfPatchesList[nrOfPatchesIdx + 1] / nrOfPatchesList[nrOfPatchesIdx] &&
|
||||
desiredPatchSize / found > nrOfPatchesList[nrOfPatchesIdx - 1] / nrOfPatchesList[nrOfPatchesIdx] ) {
|
||||
return {x: found, y: found};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
optimalPatchSize = findPatchSizeForDivisors(common);
|
||||
if (!optimalPatchSize) {
|
||||
optimalPatchSize = findPatchSizeForDivisors(this._computeDivisors(wideSide));
|
||||
if (!optimalPatchSize) {
|
||||
optimalPatchSize = findPatchSizeForDivisors((this._computeDivisors(desiredPatchSize * nrOfPatches)));
|
||||
}
|
||||
}
|
||||
return optimalPatchSize;
|
||||
};
|
||||
|
||||
CVUtils._parseCSSDimensionValues = function(value) {
|
||||
var dimension = {
|
||||
value: parseFloat(value),
|
||||
unit: value.indexOf("%") === value.length - 1 ? "%" : "%"
|
||||
};
|
||||
|
||||
return dimension;
|
||||
};
|
||||
|
||||
CVUtils._dimensionsConverters = {
|
||||
top: function(dimension, context) {
|
||||
if (dimension.unit === "%") {
|
||||
return Math.floor(context.height * (dimension.value / 100));
|
||||
}
|
||||
},
|
||||
right: function(dimension, context) {
|
||||
if (dimension.unit === "%") {
|
||||
return Math.floor(context.width - (context.width * (dimension.value / 100)));
|
||||
}
|
||||
},
|
||||
bottom: function(dimension, context) {
|
||||
if (dimension.unit === "%") {
|
||||
return Math.floor(context.height - (context.height * (dimension.value / 100)));
|
||||
}
|
||||
},
|
||||
left: function(dimension, context) {
|
||||
if (dimension.unit === "%") {
|
||||
return Math.floor(context.width * (dimension.value / 100));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CVUtils.computeImageArea = function(inputWidth, inputHeight, area) {
|
||||
var context = {width: inputWidth, height: inputHeight};
|
||||
|
||||
var parsedArea = Object.keys(area).reduce(function(result, key) {
|
||||
var value = area[key],
|
||||
parsed = CVUtils._parseCSSDimensionValues(value),
|
||||
calculated = CVUtils._dimensionsConverters[key](parsed, context);
|
||||
|
||||
result[key] = calculated;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
sx: parsedArea.left,
|
||||
sy: parsedArea.top,
|
||||
sw: parsedArea.right - parsedArea.left,
|
||||
sh: parsedArea.bottom - parsedArea.top
|
||||
};
|
||||
};
|
||||
|
||||
export default CVUtils;
|
||||
@@ -0,0 +1,82 @@
|
||||
export default (function() {
|
||||
var events = {};
|
||||
|
||||
function getEvent(eventName) {
|
||||
if (!events[eventName]) {
|
||||
events[eventName] = {
|
||||
subscribers: []
|
||||
};
|
||||
}
|
||||
return events[eventName];
|
||||
}
|
||||
|
||||
function clearEvents(){
|
||||
events = {};
|
||||
}
|
||||
|
||||
function publishSubscription(subscription, data) {
|
||||
if (subscription.async) {
|
||||
setTimeout(function() {
|
||||
subscription.callback(data);
|
||||
}, 4);
|
||||
} else {
|
||||
subscription.callback(data);
|
||||
}
|
||||
}
|
||||
|
||||
function subscribe(event, callback, async) {
|
||||
var subscription;
|
||||
|
||||
if ( typeof callback === "function") {
|
||||
subscription = {
|
||||
callback: callback,
|
||||
async: async
|
||||
};
|
||||
} else {
|
||||
subscription = callback;
|
||||
if (!subscription.callback) {
|
||||
throw "Callback was not specified on options";
|
||||
}
|
||||
}
|
||||
|
||||
getEvent(event).subscribers.push(subscription);
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: function(event, callback, async) {
|
||||
return subscribe(event, callback, async);
|
||||
},
|
||||
publish: function(eventName, data) {
|
||||
var event = getEvent(eventName),
|
||||
subscribers = event.subscribers;
|
||||
|
||||
event.subscribers = subscribers.filter(function(subscriber) {
|
||||
publishSubscription(subscriber, data);
|
||||
return !subscriber.once;
|
||||
});
|
||||
},
|
||||
once: function(event, callback, async) {
|
||||
subscribe(event, {
|
||||
callback: callback,
|
||||
async: async,
|
||||
once: true
|
||||
});
|
||||
},
|
||||
unsubscribe: function(eventName, callback) {
|
||||
var event;
|
||||
|
||||
if (eventName) {
|
||||
event = getEvent(eventName);
|
||||
if (event && callback) {
|
||||
event.subscribers = event.subscribers.filter(function(subscriber){
|
||||
return subscriber.callback !== callback;
|
||||
});
|
||||
} else {
|
||||
event.subscribers = [];
|
||||
}
|
||||
} else {
|
||||
clearEvents();
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,41 @@
|
||||
export default {
|
||||
drawRect: function(pos, size, ctx, style){
|
||||
ctx.strokeStyle = style.color;
|
||||
ctx.fillStyle = style.color;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.strokeRect(pos.x, pos.y, size.x, size.y);
|
||||
},
|
||||
drawPath: function(path, def, ctx, style) {
|
||||
ctx.strokeStyle = style.color;
|
||||
ctx.fillStyle = style.color;
|
||||
ctx.lineWidth = style.lineWidth;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(path[0][def.x], path[0][def.y]);
|
||||
for (var j = 1; j < path.length; j++) {
|
||||
ctx.lineTo(path[j][def.x], path[j][def.y]);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
},
|
||||
drawImage: function(imageData, size, ctx) {
|
||||
var canvasData = ctx.getImageData(0, 0, size.x, size.y),
|
||||
data = canvasData.data,
|
||||
imageDataPos = imageData.length,
|
||||
canvasDataPos = data.length,
|
||||
value;
|
||||
|
||||
if (canvasDataPos / imageDataPos !== 4) {
|
||||
return false;
|
||||
}
|
||||
while (imageDataPos--){
|
||||
value = imageData[imageDataPos];
|
||||
data[--canvasDataPos] = 255;
|
||||
data[--canvasDataPos] = value;
|
||||
data[--canvasDataPos] = value;
|
||||
data[--canvasDataPos] = value;
|
||||
}
|
||||
ctx.putImageData(canvasData, 0, 0);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,347 @@
|
||||
import SubImage from './subImage';
|
||||
import CVUtils from '../common/cv_utils';
|
||||
import ArrayHelper from '../common/array_helper';
|
||||
import {vec2} from 'gl-matrix';
|
||||
|
||||
/**
|
||||
* Represents a basic image combining the data and size.
|
||||
* In addition, some methods for manipulation are contained.
|
||||
* @param size {x,y} The size of the image in pixel
|
||||
* @param data {Array} If given, a flat array containing the pixel data
|
||||
* @param ArrayType {Type} If given, the desired DataType of the Array (may be typed/non-typed)
|
||||
* @param initialize {Boolean} Indicating if the array should be initialized on creation.
|
||||
* @returns {ImageWrapper}
|
||||
*/
|
||||
function ImageWrapper(size, data, ArrayType, initialize) {
|
||||
if (!data) {
|
||||
if (ArrayType) {
|
||||
this.data = new ArrayType(size.x * size.y);
|
||||
if (ArrayType === Array && initialize) {
|
||||
ArrayHelper.init(this.data, 0);
|
||||
}
|
||||
} else {
|
||||
this.data = new Uint8Array(size.x * size.y);
|
||||
if (Uint8Array === Array && initialize) {
|
||||
ArrayHelper.init(this.data, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.data = data;
|
||||
}
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* tests if a position is within the image with a given offset
|
||||
* @param imgRef {x, y} The location to test
|
||||
* @param border Number the padding value in pixel
|
||||
* @returns {Boolean} true if location inside the image's border, false otherwise
|
||||
* @see cvd/image.h
|
||||
*/
|
||||
ImageWrapper.prototype.inImageWithBorder = function(imgRef, border) {
|
||||
return (imgRef.x >= border)
|
||||
&& (imgRef.y >= border)
|
||||
&& (imgRef.x < (this.size.x - border))
|
||||
&& (imgRef.y < (this.size.y - border));
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs bilinear sampling
|
||||
* @param inImg Image to extract sample from
|
||||
* @param x the x-coordinate
|
||||
* @param y the y-coordinate
|
||||
* @returns the sampled value
|
||||
* @see cvd/vision.h
|
||||
*/
|
||||
ImageWrapper.sample = function(inImg, x, y) {
|
||||
var lx = Math.floor(x);
|
||||
var ly = Math.floor(y);
|
||||
var w = inImg.size.x;
|
||||
var base = ly * inImg.size.x + lx;
|
||||
var a = inImg.data[base + 0];
|
||||
var b = inImg.data[base + 1];
|
||||
var c = inImg.data[base + w];
|
||||
var d = inImg.data[base + w + 1];
|
||||
var e = a - b;
|
||||
x -= lx;
|
||||
y -= ly;
|
||||
|
||||
var result = Math.floor(x * (y * (e - c + d) - e) + y * (c - a) + a);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a given array. Sets each element to zero.
|
||||
* @param array {Array} The array to initialize
|
||||
*/
|
||||
ImageWrapper.clearArray = function(array) {
|
||||
var l = array.length;
|
||||
while (l--) {
|
||||
array[l] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a {SubImage} from the current image ({this}).
|
||||
* @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
|
||||
* @param size {ImageRef} The size of the resulting image
|
||||
* @returns {SubImage} A shared part of the original image
|
||||
*/
|
||||
ImageWrapper.prototype.subImage = function(from, size) {
|
||||
return new SubImage(from, size, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an {ImageWrapper) and copies the needed underlying image-data area
|
||||
* @param imageWrapper {ImageWrapper} The target {ImageWrapper} where the data should be copied
|
||||
* @param from {ImageRef} The location where to copy from (top-left location)
|
||||
*/
|
||||
ImageWrapper.prototype.subImageAsCopy = function(imageWrapper, from) {
|
||||
var sizeY = imageWrapper.size.y, sizeX = imageWrapper.size.x;
|
||||
var x, y;
|
||||
for ( x = 0; x < sizeX; x++) {
|
||||
for ( y = 0; y < sizeY; y++) {
|
||||
imageWrapper.data[y * sizeX + x] = this.data[(from.y + y) * this.size.x + from.x + x];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ImageWrapper.prototype.copyTo = function(imageWrapper) {
|
||||
var length = this.data.length, srcData = this.data, dstData = imageWrapper.data;
|
||||
|
||||
while (length--) {
|
||||
dstData[length] = srcData[length];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a given pixel position from the image
|
||||
* @param x {Number} The x-position
|
||||
* @param y {Number} The y-position
|
||||
* @returns {Number} The grayscale value at the pixel-position
|
||||
*/
|
||||
ImageWrapper.prototype.get = function(x, y) {
|
||||
return this.data[y * this.size.x + x];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a given pixel position from the image
|
||||
* @param x {Number} The x-position
|
||||
* @param y {Number} The y-position
|
||||
* @returns {Number} The grayscale value at the pixel-position
|
||||
*/
|
||||
ImageWrapper.prototype.getSafe = function(x, y) {
|
||||
var i;
|
||||
|
||||
if (!this.indexMapping) {
|
||||
this.indexMapping = {
|
||||
x: [],
|
||||
y: []
|
||||
};
|
||||
for (i = 0; i < this.size.x; i++) {
|
||||
this.indexMapping.x[i] = i;
|
||||
this.indexMapping.x[i + this.size.x] = i;
|
||||
}
|
||||
for (i = 0; i < this.size.y; i++) {
|
||||
this.indexMapping.y[i] = i;
|
||||
this.indexMapping.y[i + this.size.y] = i;
|
||||
}
|
||||
}
|
||||
return this.data[(this.indexMapping.y[y + this.size.y]) * this.size.x + this.indexMapping.x[x + this.size.x]];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a given pixel position in the image
|
||||
* @param x {Number} The x-position
|
||||
* @param y {Number} The y-position
|
||||
* @param value {Number} The grayscale value to set
|
||||
* @returns {ImageWrapper} The Image itself (for possible chaining)
|
||||
*/
|
||||
ImageWrapper.prototype.set = function(x, y, value) {
|
||||
this.data[y * this.size.x + x] = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the border of the image (1 pixel) to zero
|
||||
*/
|
||||
ImageWrapper.prototype.zeroBorder = function() {
|
||||
var i, width = this.size.x, height = this.size.y, data = this.data;
|
||||
for ( i = 0; i < width; i++) {
|
||||
data[i] = data[(height - 1) * width + i] = 0;
|
||||
}
|
||||
for ( i = 1; i < height - 1; i++) {
|
||||
data[i * width] = data[i * width + (width - 1)] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Inverts a binary image in place
|
||||
*/
|
||||
ImageWrapper.prototype.invert = function() {
|
||||
var data = this.data, length = data.length;
|
||||
|
||||
while (length--) {
|
||||
data[length] = data[length] ? 0 : 1;
|
||||
}
|
||||
};
|
||||
|
||||
ImageWrapper.prototype.convolve = function(kernel) {
|
||||
var x, y, kx, ky, kSize = (kernel.length / 2) | 0, accu = 0;
|
||||
for ( y = 0; y < this.size.y; y++) {
|
||||
for ( x = 0; x < this.size.x; x++) {
|
||||
accu = 0;
|
||||
for ( ky = -kSize; ky <= kSize; ky++) {
|
||||
for ( kx = -kSize; kx <= kSize; kx++) {
|
||||
accu += kernel[ky + kSize][kx + kSize] * this.getSafe(x + kx, y + ky);
|
||||
}
|
||||
}
|
||||
this.data[y * this.size.x + x] = accu;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ImageWrapper.prototype.moments = function(labelcount) {
|
||||
var data = this.data,
|
||||
x,
|
||||
y,
|
||||
height = this.size.y,
|
||||
width = this.size.x,
|
||||
val,
|
||||
ysq,
|
||||
labelsum = [],
|
||||
i,
|
||||
label,
|
||||
mu11,
|
||||
mu02,
|
||||
mu20,
|
||||
x_,
|
||||
y_,
|
||||
tmp,
|
||||
result = [],
|
||||
PI = Math.PI,
|
||||
PI_4 = PI / 4;
|
||||
|
||||
if (labelcount <= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for ( i = 0; i < labelcount; i++) {
|
||||
labelsum[i] = {
|
||||
m00: 0,
|
||||
m01: 0,
|
||||
m10: 0,
|
||||
m11: 0,
|
||||
m02: 0,
|
||||
m20: 0,
|
||||
theta: 0,
|
||||
rad: 0
|
||||
};
|
||||
}
|
||||
|
||||
for ( y = 0; y < height; y++) {
|
||||
ysq = y * y;
|
||||
for ( x = 0; x < width; x++) {
|
||||
val = data[y * width + x];
|
||||
if (val > 0) {
|
||||
label = labelsum[val - 1];
|
||||
label.m00 += 1;
|
||||
label.m01 += y;
|
||||
label.m10 += x;
|
||||
label.m11 += x * y;
|
||||
label.m02 += ysq;
|
||||
label.m20 += x * x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( i = 0; i < labelcount; i++) {
|
||||
label = labelsum[i];
|
||||
if (!isNaN(label.m00) && label.m00 !== 0) {
|
||||
x_ = label.m10 / label.m00;
|
||||
y_ = label.m01 / label.m00;
|
||||
mu11 = label.m11 / label.m00 - x_ * y_;
|
||||
mu02 = label.m02 / label.m00 - y_ * y_;
|
||||
mu20 = label.m20 / label.m00 - x_ * x_;
|
||||
tmp = (mu02 - mu20) / (2 * mu11);
|
||||
tmp = 0.5 * Math.atan(tmp) + (mu11 >= 0 ? PI_4 : -PI_4 ) + PI;
|
||||
label.theta = (tmp * 180 / PI + 90) % 180 - 90;
|
||||
if (label.theta < 0) {
|
||||
label.theta += 180;
|
||||
}
|
||||
label.rad = tmp > PI ? tmp - PI : tmp;
|
||||
label.vec = vec2.clone([Math.cos(tmp), Math.sin(tmp)]);
|
||||
result.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the {ImageWrapper} in a given canvas
|
||||
* @param canvas {Canvas} The canvas element to write to
|
||||
* @param scale {Number} Scale which is applied to each pixel-value
|
||||
*/
|
||||
ImageWrapper.prototype.show = function(canvas, scale) {
|
||||
var ctx,
|
||||
frame,
|
||||
data,
|
||||
current,
|
||||
pixel,
|
||||
x,
|
||||
y;
|
||||
|
||||
if (!scale) {
|
||||
scale = 1.0;
|
||||
}
|
||||
ctx = canvas.getContext('2d');
|
||||
canvas.width = this.size.x;
|
||||
canvas.height = this.size.y;
|
||||
frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
data = frame.data;
|
||||
current = 0;
|
||||
for (y = 0; y < this.size.y; y++) {
|
||||
for (x = 0; x < this.size.x; x++) {
|
||||
pixel = y * this.size.x + x;
|
||||
current = this.get(x, y) * scale;
|
||||
data[pixel * 4 + 0] = current;
|
||||
data[pixel * 4 + 1] = current;
|
||||
data[pixel * 4 + 2] = current;
|
||||
data[pixel * 4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
//frame.data = data;
|
||||
ctx.putImageData(frame, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the {SubImage} in a given canvas
|
||||
* @param canvas {Canvas} The canvas element to write to
|
||||
* @param scale {Number} Scale which is applied to each pixel-value
|
||||
*/
|
||||
ImageWrapper.prototype.overlay = function(canvas, scale, from) {
|
||||
if (!scale || scale < 0 || scale > 360) {
|
||||
scale = 360;
|
||||
}
|
||||
var hsv = [0, 1, 1];
|
||||
var rgb = [0, 0, 0];
|
||||
var whiteRgb = [255, 255, 255];
|
||||
var blackRgb = [0, 0, 0];
|
||||
var result = [];
|
||||
var ctx = canvas.getContext('2d');
|
||||
var frame = ctx.getImageData(from.x, from.y, this.size.x, this.size.y);
|
||||
var data = frame.data;
|
||||
var length = this.data.length;
|
||||
while (length--) {
|
||||
hsv[0] = this.data[length] * scale;
|
||||
result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : CVUtils.hsv2rgb(hsv, rgb);
|
||||
data[length * 4 + 0] = result[0];
|
||||
data[length * 4 + 1] = result[1];
|
||||
data[length * 4 + 2] = result[2];
|
||||
data[length * 4 + 3] = 255;
|
||||
}
|
||||
ctx.putImageData(frame, from.x, from.y);
|
||||
};
|
||||
|
||||
export default ImageWrapper;
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Construct representing a part of another {ImageWrapper}. Shares data
|
||||
* between the parent and the child.
|
||||
* @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
|
||||
* @param size {ImageRef} The size of the resulting image
|
||||
* @param I {ImageWrapper} The {ImageWrapper} to share from
|
||||
* @returns {SubImage} A shared part of the original image
|
||||
*/
|
||||
function SubImage(from, size, I) {
|
||||
if (!I) {
|
||||
I = {
|
||||
data: null,
|
||||
size: size
|
||||
};
|
||||
}
|
||||
this.data = I.data;
|
||||
this.originalSize = I.size;
|
||||
this.I = I;
|
||||
|
||||
this.from = from;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the {SubImage} in a given canvas
|
||||
* @param canvas {Canvas} The canvas element to write to
|
||||
* @param scale {Number} Scale which is applied to each pixel-value
|
||||
*/
|
||||
SubImage.prototype.show = function(canvas, scale) {
|
||||
var ctx,
|
||||
frame,
|
||||
data,
|
||||
current,
|
||||
y,
|
||||
x,
|
||||
pixel;
|
||||
|
||||
if (!scale) {
|
||||
scale = 1.0;
|
||||
}
|
||||
ctx = canvas.getContext('2d');
|
||||
canvas.width = this.size.x;
|
||||
canvas.height = this.size.y;
|
||||
frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
data = frame.data;
|
||||
current = 0;
|
||||
for (y = 0; y < this.size.y; y++) {
|
||||
for (x = 0; x < this.size.x; x++) {
|
||||
pixel = y * this.size.x + x;
|
||||
current = this.get(x, y) * scale;
|
||||
data[pixel * 4 + 0] = current;
|
||||
data[pixel * 4 + 1] = current;
|
||||
data[pixel * 4 + 2] = current;
|
||||
data[pixel * 4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
frame.data = data;
|
||||
ctx.putImageData(frame, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a given pixel position from the {SubImage}
|
||||
* @param x {Number} The x-position
|
||||
* @param y {Number} The y-position
|
||||
* @returns {Number} The grayscale value at the pixel-position
|
||||
*/
|
||||
SubImage.prototype.get = function(x, y) {
|
||||
return this.data[(this.from.y + y) * this.originalSize.x + this.from.x + x];
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the underlying data from a given {ImageWrapper}
|
||||
* @param image {ImageWrapper} The updated image
|
||||
*/
|
||||
SubImage.prototype.updateData = function(image) {
|
||||
this.originalSize = image.size;
|
||||
this.data = image.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the position of the shared area
|
||||
* @param from {x,y} The new location
|
||||
* @returns {SubImage} returns {this} for possible chaining
|
||||
*/
|
||||
SubImage.prototype.updateFrom = function(from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
};
|
||||
|
||||
export default (SubImage);
|
||||
Reference in New Issue
Block a user