//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.qmaclone.server.handwriting;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class StrokeTransformer {
private static final double EPS = 1e-15;
public final Map<Character, double[][]> transform(Map<Character, double[][][][]> handwritingData) {
final Map<Character, double[][]> result = new HashMap<Character, double[][]>();
for (Entry<Character, double[][][][]> entry : handwritingData.entrySet()) {
final List<double[]> vectors = new ArrayList<double[]>();
for (double[][][] strokes : entry.getValue()) {
scale(strokes);
vectors.add(transform(strokes));
}
result.put(entry.getKey(), vectors.toArray(new double[0][]));
}
return result;
}
public static void scale(double[][][] strokes) {
double minX = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
for (double[][] stroke : strokes) {
for (double[] point : stroke) {
minX = Math.min(minX, point[0]);
maxX = Math.max(maxX, point[0]);
minY = Math.min(minY, point[1]);
maxY = Math.max(maxY, point[1]);
}
}
if (minX == maxX) {
minX = 0.0;
maxX = 1.0;
}
if (minY == maxY) {
minY = 0.0;
maxY = 1.0;
}
for (double[][] stroke : strokes) {
for (double[] point : stroke) {
point[0] = (point[0] - minX) / (maxX - minX);
point[1] = (point[1] - minY) / (maxY - minY);
}
}
}
public double[] transform(double[][][] strokes) {
final double[] vector = new double[strokes.length * 5];
int offset = 0;
// 各画を登録
for (double[][] stroke : strokes) {
final double[] values = transform(stroke);
for (int i = 0; i < values.length; ++i) {
vector[offset++] = values[i];
}
}
return vector;
}
// 三好義昭,"手書き文字評価のための特徴抽出"
private double[] transform(double[][] stroke) {
double[] result = new double[5];
// 長さ
final double lengthSum = calculateLength(stroke);
result[0] = lengthSum;
// 中心位置
final double[] center = calculateCenter(stroke, lengthSum);
result[1] = center[0];
result[2] = center[1];
// 方向
final double direction = calculateDirection(stroke);
result[3] = direction;
// 曲直
final double curvature = calculateCurvature(stroke, center);
result[4] = curvature;
return result;
}
private double calculateCurvature(double[][] stroke, final double[] center) {
// 最小二乗法近似
// http://atsugi5761455.fc2web.com/calking16.html
final int n = stroke.length;
double sumX = 0;
double sumX2 = 0;
double sumX3 = 0;
double sumX4 = 0;
double sumY = 0;
double sumXY = 0;
double sumX2Y = 0;
for (double[] point : stroke) {
final double x = point[0];
final double y = point[1];
sumX += x;
sumX2 += x * x;
sumX3 += x * x * x;
sumX4 += x * x * x * x;
sumY += y;
sumXY += x * y;
sumX2Y += x * x * y;
}
// Miyazaki's Technique 逆行列
// http://www.cvl.iis.u-tokyo.ac.jp/~miyazaki/tech/tech23.html
final double a11 = n;
final double a12 = sumX;
final double a13 = sumX2;
final double a21 = sumX;
final double a22 = sumX2;
final double a23 = sumX3;
final double a31 = sumX2;
final double a32 = sumX3;
final double a33 = sumX4;
// 行列式の0チェックは(ry
// (ry しちゃダメだったorz
final double detA = a11 * a22 * a33 + a21 * a32 * a13 + a31 * a12 * a23 - a11 * a32 * a23 - a31 * a22 * a13 - a21 * a12 * a33;
if (Math.abs(detA) < EPS) {
return 0;
}
// final double b11 = (a22 * a33 - a23 * a32) / detA;
// final double b12 = (a13 * a32 - a12 * a33) / detA;
// final double b13 = (a12 * a23 - a13 * a22) / detA;
final double b21 = (a23 * a31 - a21 * a33) / detA;
final double b22 = (a11 * a33 - a13 * a31) / detA;
final double b23 = (a13 * a21 - a11 * a23) / detA;
final double b31 = (a21 * a32 - a22 * a31) / detA;
final double b32 = (a12 * a31 - a11 * a32) / detA;
final double b33 = (a11 * a22 - a12 * a21) / detA;
final double c1 = sumY;
final double c2 = sumXY;
final double c3 = sumX2Y;
// final double d1 = b11 * c1 + b12 * c2 * b13 * c3;
final double d2 = b21 * c1 + b22 * c2 * b23 * c3;
final double d3 = b31 * c1 + b32 * c2 * b33 * c3;
final double centerX = center[0];
double r = 2 * d3 * centerX + d2;
final double curvature = 2 * d3 / Math.pow(1 + r * r, 1.5);
// System.out.printf("%30.20f\n", curvature);
return curvature;
}
private double calculateDirection(double[][] stroke) {
final int n = stroke.length;
double sumXY = 0;
double sumX = 0;
double sumY = 0;
double sumX2 = 0;
for (double[] point : stroke) {
final double x = point[0];
final double y = point[1];
sumXY += x * y;
sumX += x;
sumY += y;
sumX2 += x * x;
}
final double arg = Math.atan2(n * sumXY - sumX * sumY, n * sumX2 - sumX * sumX);
return arg;
}
private double[] calculateCenter(double[][] stroke, double lengthSum) {
final double[] center = new double[2];
double length = 0;
for (int i = 0; i < stroke.length - 1; ++i) {
final double x0 = stroke[i][0];
final double y0 = stroke[i][1];
final double x1 = stroke[i + 1][0];
final double y1 = stroke[i + 1][1];
final double dx = x1 - x0;
final double dy = y1 - y0;
final double distance = Math.sqrt(dx * dx + dy * dy);
final double nextLength = length + distance;
if (nextLength > lengthSum / 2) {
final double r = (lengthSum / 2 - length) / distance;
center[0] = (1 - r) * x0 + r * x1;
center[1] = (1 - r) * y0 + r * y1;
break;
}
length = nextLength;
}
return center;
}
private double calculateLength(double[][] stroke) {
double lengthSum = 0;
for (int i = 0; i < stroke.length - 1; ++i) {
final double x0 = stroke[i][0];
final double y0 = stroke[i][1];
final double x1 = stroke[i + 1][0];
final double y1 = stroke[i + 1][1];
final double dx = x1 - x0;
final double dy = y1 - y0;
lengthSum += Math.sqrt(dx * dx + dy * dy);
}
return lengthSum;
}
}