Commit 0bbd229b by Иван Кубота

switch matching to lab

parent a403f608
var RGB2XYZ = function(R, G, B) {
if (R > 255) {
console.warn('Red value was higher than 255. It has been set to 255.');
R = 255;
} else if (R < 0) {
console.warn('Red value was smaller than 0. It has been set to 0.');
R = 0;
}
if (G > 255) {
console.warn('Green value was higher than 255. It has been set to 255.');
G = 255;
} else if (G < 0) {
console.warn('Green value was smaller than 0. It has been set to 0.');
G = 0;
}
if (B > 255) {
console.warn('Blue value was higher than 255. It has been set to 255.');
B = 255;
} else if (B < 0) {
console.warn('Blue value was smaller than 0. It has been set to 0.');
B = 0;
}
var r = R / 255;
var g = G / 255;
var b = B / 255;
// step 1
if (r > 0.04045) {
r = Math.pow(((r + 0.055) / 1.055), 2.4);
} else {
r = r / 12.92;
}
if (g > 0.04045) {
g = Math.pow(((g + 0.055) / 1.055), 2.4);
} else {
g = g / 12.92;
}
if (b > 0.04045) {
b = Math.pow(((b + 0.055) / 1.055), 2.4);
} else {
b = b / 12.92;
}
// step 2
r = r * 100;
g = g * 100;
b = b * 100;
// step 3
var X = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
var Y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
var Z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
return [X, Y, Z];
}
var XYZ2LAB = function(X, Y, Z) {
// using 10o Observer (CIE 1964)
// CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
var ReferenceX = 94.811;
var ReferenceY = 100;
var ReferenceZ = 107.304;
// step 1
var x = X / ReferenceX;
var y = Y / ReferenceY;
var z = Z / ReferenceZ;
// step 2
if (x > 0.008856) {
x = Math.pow(x, (1 / 3));
} else {
x = (7.787 * x) + (16 / 116);
}
if (y > 0.008856) {
y = Math.pow(y, (1 / 3));
} else {
y = (7.787 * y) + (16 / 116);
}
if (z > 0.008856) {
z = Math.pow(z, (1 / 3));
} else {
z = (7.787 * z) + (16 / 116);
}
// step 3
var L = (116 * y) - 16;
var A = 500 * (x - y);
var B = 200 * (y - z);
return [L, A, B];
};
function LAB2XYZ(L, A, B) {
// using 10o Observer (CIE 1964)
// CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
var ReferenceX = 94.811;
var ReferenceY = 100;
var ReferenceZ = 107.304;
var var_Y = ( L + 16 ) / 116;
var var_X = A / 500 + var_Y;
var var_Z = var_Y - B / 200;
if ( Math.pow(var_Y, 3) > 0.008856 ) {
var_Y = Math.pow(var_Y, 3);
} else {
var_Y = ( var_Y - 16 / 116 ) / 7.787;
}
if ( Math.pow(var_X, 3) > 0.008856 ) {
var_X = Math.pow(var_X, 3);
} else {
var_X = ( var_X - 16 / 116 ) / 7.787;
}
if ( Math.pow(var_Z, 3) > 0.008856 ) {
var_Z = Math.pow(var_Z, 3);
} else {
var_Z = ( var_Z - 16 / 116 ) / 7.787;
}
var X = var_X * ReferenceX;
var Y = var_Y * ReferenceY;
var Z = var_Z * ReferenceZ;
return [X, Y, Z];
}
var XYZ2RGB = function(X, Y, Z) {
var var_X = X / 100;
var var_Y = Y / 100;
var var_Z = Z / 100;
var var_R = (var_X * 3.2406) + (var_Y * -1.5372) + (var_Z * -0.4986);
var var_G = (var_X * -0.9689) + (var_Y * 1.8758) + (var_Z * 0.0415);
var var_B = (var_X * 0.0557) + (var_Y * -0.2040) + (var_Z * 1.0570);
if ( var_R > 0.0031308 ) {
var_R = 1.055 * Math.pow(var_R, (1 / 2.4) ) - 0.055;
} else {
var_R = 12.92 * var_R;
}
if ( var_G > 0.0031308 ) {
var_G = 1.055 * Math.pow(var_G, (1 / 2.4) ) - 0.055;
} else {
var_G = 12.92 * var_G;
}
if ( var_B > 0.0031308 ) {
var_B = 1.055 * Math.pow(var_B, (1 / 2.4) ) - 0.055;
} else {
var_B = 12.92 * var_B;
}
var R = Math.round(var_R * 255);
var G = Math.round(var_G * 255);
var B = Math.round(var_B * 255);
return [R, G, B];
}
function rgbToLab(r,g,b) {
return XYZ2LAB.apply(null, RGB2XYZ(r,g,b))
};
function labToRgb(r,g,b) {
return XYZ2RGB.apply(null, LAB2XYZ(r,g,b))
};
function rgbToHsv(r, g, b) {
r /= 255, g /= 255, b /= 255;
......@@ -21,6 +174,79 @@ function rgbToHsv(r, g, b) {
return [ h, s, v ];
}
var labDistance = function(l1,l2) {
return DeltaE00(l1,l2);
};
var DeltaE00 = function(lab1, lab2) {
var l1 = lab1[0], a1 = lab1[1], b1 = lab1[2],
l2 = lab2[0], a2 = lab2[1], b2 = lab2[2];
// Utility functions added to Math Object
Math.rad2deg = function(rad) {
return 360 * rad / (2 * Math.PI);
};
Math.deg2rad = function(deg) {
return (2 * Math.PI * deg) / 360;
};
// Start Equation
// Equation exist on the following URL http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
var avgL = (l1 + l2) / 2;
var C1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2));
var C2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2));
var avgC = (C1 + C2) / 2;
var G = (1 - Math.sqrt(Math.pow(avgC, 7) / (Math.pow(avgC, 7) + Math.pow(25, 7)))) / 2;
var A1p = a1 * (1 + G);
var A2p = a2 * (1 + G);
var C1p = Math.sqrt(Math.pow(A1p, 2) + Math.pow(b1, 2));
var C2p = Math.sqrt(Math.pow(A2p, 2) + Math.pow(b2, 2));
var avgCp = (C1p + C2p) / 2;
var h1p = Math.rad2deg(Math.atan2(b1, A1p));
if (h1p < 0) {
h1p = h1p + 360;
}
var h2p = Math.rad2deg(Math.atan2(b2, A2p));
if (h2p < 0) {
h2p = h2p + 360;
}
var avghp = Math.abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h1p) / 2;
var T = 1 - 0.17 * Math.cos(Math.deg2rad(avghp - 30)) + 0.24 * Math.cos(Math.deg2rad(2 * avghp)) + 0.32 * Math.cos(Math.deg2rad(3 * avghp + 6)) - 0.2 * Math.cos(Math.deg2rad(4 * avghp - 63));
var deltahp = h2p - h1p;
if (Math.abs(deltahp) > 180) {
if (h2p <= h1p) {
deltahp += 360;
} else {
deltahp -= 360;
}
}
var delta_lp = l2 - l1;
var delta_cp = C2p - C1p;
deltahp = 2 * Math.sqrt(C1p * C2p) * Math.sin(Math.deg2rad(deltahp) / 2);
var Sl = 1 + ((0.015 * Math.pow(avgL - 50, 2)) / Math.sqrt(20 + Math.pow(avgL - 50, 2)));
var Sc = 1 + 0.045 * avgCp;
var Sh = 1 + 0.015 * avgCp * T;
var deltaro = 30 * Math.exp(-(Math.pow((avghp - 275) / 25, 2)));
var Rc = 2 * Math.sqrt(Math.pow(avgCp, 7) / (Math.pow(avgCp, 7) + Math.pow(25, 7)));
var Rt = -Rc * Math.sin(2 * Math.deg2rad(deltaro));
var kl = 1;
var kc = 1;
var kh = 1;
var deltaE = Math.sqrt(Math.pow(delta_lp / (kl * Sl), 2) + Math.pow(delta_cp / (kc * Sc), 2) + Math.pow(deltahp / (kh * Sh), 2) + Rt * (delta_cp / (kc * Sc)) * (deltahp / (kh * Sh)));
return deltaE;
};
/**
......@@ -56,7 +282,7 @@ function hsvToRgb(h, s, v) {
}
var extractColors = function( ctx, where, k, debug){
var extractColors = function( ctx, where, k, production){
var points = [].concat.apply( [], where.map( function( polygon ){
......@@ -80,44 +306,54 @@ var extractColors = function( ctx, where, k, debug){
var r, g, b;
var colors = [];
var debugPixels = document.getElementById('ccolors').getContext('2d');
var debugData = debugPixels.getImageData( 0, 0, 100, 480 ),
dd = debugData.data;
var pt = (extracting*100*120*4)
if( !production ){
var debugPixels = document.getElementById( 'ccolors' ).getContext( '2d' );
var debugData = debugPixels.getImageData( 0, 0, 100, 480 ),
dd = debugData.data;
}
var pt = ( extracting * 100 * 120 * 4 )
for( var i = 0, _i = points.length; i < _i; i++ ){
var p = points[ i ], pointer = ( p[ 0 ] - minX + ( p[ 1 ] - minY ) * w ) * 4;
r = data[ pointer ];
g = data[ pointer + 1 ];
b = data[ pointer + 2 ];
pt = ((extracting*100*120+i*2+((i/50)|0)*100)*4);
dd[pt++] = r; dd[pt++] = g; dd[pt++] = b;
dd[pt++]=255;
dd[pt++] = r; dd[pt++] = g; dd[pt++] = b;
dd[pt++]=255;
pt = ((extracting*100*120+i*2+((i/50)|0)*100)*4)+100*4;
dd[pt++] = r; dd[pt++] = g; dd[pt++] = b;
dd[pt++]=255;
dd[pt++] = r; dd[pt++] = g; dd[pt++] = b;
dd[pt++]=255;
if( !production ){
pt = ( ( extracting * 100 * 120 + i * 2 + ( ( i / 50 ) | 0 ) * 100 ) * 4 );
dd[ pt++ ] = r;
dd[ pt++ ] = g;
dd[ pt++ ] = b;
dd[ pt++ ] = 255;
dd[ pt++ ] = r;
dd[ pt++ ] = g;
dd[ pt++ ] = b;
dd[ pt++ ] = 255;
pt = ( ( extracting * 100 * 120 + i * 2 + ( ( i / 50 ) | 0 ) * 100 ) * 4 ) + 100 * 4;
dd[ pt++ ] = r;
dd[ pt++ ] = g;
dd[ pt++ ] = b;
dd[ pt++ ] = 255;
dd[ pt++ ] = r;
dd[ pt++ ] = g;
dd[ pt++ ] = b;
dd[ pt++ ] = 255;
}
//data[ pointer + 2 ] = 255
colors.push( rgbToHsv( r, g, b ) );
colors.push( rgbToLab( r, g, b ) );
}
if( !production ){
debugPixels.putImageData( debugData, 0, 0 );
}
debugPixels.putImageData( debugData, 0, 0 );
//ctx2.putImageData( imageData, minX, minY );
var means = new KMeans();
var result = { means: means, clusters: means.cluster( colors, k, hsvDistance ) };
var result = { means: means, clusters: means.cluster( colors, k, labDistance ) };
var amount = 0;
for( var i = 0, _i = result.clusters.length; i < _i; i++ ){
var cluster = result.clusters[ i ] || [];
......@@ -128,29 +364,41 @@ var extractColors = function( ctx, where, k, debug){
return { color: c, normalized: c.length / amount };
} )
.filter( function( c ){
return c.normalized > 1 / (k*3);
return c.normalized > 1 / ( k * 2.5 );
} )
.sort( function( a, b ){
return a.normalized - b.normalized;
} )
.map( function( c ){
return c.color.sort(colorSort)[0];//result.means.centroids.slice().sort(distanceToHSV(c.color[0]))[0];
var l = 0, a = 0, b = 0;
for( var i = 0, _i = c.color.length; i < _i; i++ ){
var colorElement = c.color[ i ];
l+=colorElement[0];
a+=colorElement[1];
b+=colorElement[2];
}
return [
l/c.color.length,
a/c.color.length,
b/c.color.length
];
//c.color.sort( colorSort )[ 0 ];//result.means.centroids.slice().sort(distanceToHSV(c.color[0]))[0];
} );
var similars = filteredResult.map(function(c, i) {
return {color: c, i: i, similar: [i]}
});
var similars = filteredResult.map( function( c, i ){
return { color: c, i: i, similar: [ i ] }
} );
// O^2 similarity search
for( var i = 0, _i = similars.length; i < _i; i++ ){
var c1 = similars[ i ];
for( var j = 0, _j = similars.length; j < _j; j++ ){
if(i === j)
if( i === j )
continue;
var c2 = similars[ j ];
if(Math.abs(hsvDistance(c1.color, c2.color))<similarColor){
if(c1.similar.indexOf(j) === -1){
if( Math.abs( labDistance( c1.color, c2.color ) ) < similarColor ){
if( c1.similar.indexOf( j ) === -1 ){
c1.similar.push( j );
c2.similar.push( i );
}
......@@ -159,38 +407,35 @@ var extractColors = function( ctx, where, k, debug){
}
var used = {};
var different = similars.reduce(function(store, c) {
if(!(c.i in used))
store.push(c);
var different = similars.reduce( function( store, c ){
if( !( c.i in used ) )
store.push( c );
for( var i = 0, _i = c.similar.length; i < _i; i++ ){
var similarElement = c.similar[ i ];
var item = similars[similarElement];
if(!(item.i in used)){
used[item.i] = true;
var item = similars[ similarElement ];
if( !( item.i in used ) ){
used[ item.i ] = true;
}
}
return store;
}, []);
}, [] );
if(debug){
similars, result
}
filteredResult = different.map(function(item) {
filteredResult = different.map( function( item ){
return item.color;
});
} );
return filteredResult.map(function(color) {
return {color: color, distance: hsvDistance(color, filteredResult[0])};
}).filter(function(color) {
var delta = Math.abs(color.distance);
return delta <0.0000001 || delta > 0.03;
});
return filteredResult.map( function( color ){
return { color: color, distance: labDistance( color, filteredResult[ 0 ] ) };
} ).filter( function( color ){
var delta = Math.abs( color.distance );
return delta < 0.0000001 || delta > 15;
} );
};
var colorSort = function(a,b){return (-(a[2]-b[2]))+(-(a[1]-b[1])/2)};
var similarColor = 0.05;
var similarColor = 5;//0.05;
var abs = Math.abs, min = Math.min;
var hsvDistance = function( c1, c2 ){
var dh = min( abs( c2[ 0 ] - c1[ 0 ] ), 1 - abs( c2[ 0 ] - c1[ 0 ] ) ),
......@@ -213,11 +458,11 @@ function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
var binds = [];
var extractor = function(partName, ctx, from, k, debug) {
var extractor = function(partName, ctx, from, k, production) {
extracting++;
var colors = extractColors(ctx, from, k, debug)
var colors = extractColors(ctx, from, k, production)
.map(function(m) {
return {color: hsvToRgb.apply(null, m.color).map(function(a){return a|0}), hsv: m.color, distance: m.distance};
return {color: labToRgb.apply(null, m.color).map(function(a){return a|0}), hsv: m.color, distance: m.distance};
});
binds.forEach(function(b) {
......@@ -225,27 +470,47 @@ var extractor = function(partName, ctx, from, k, debug) {
});
binds = [];
if(production){
D.div({cls: 'colors', renderTo: document.getElementById(partName+'-bg') },
colors.map(function(m){
return D.div({style: 'background:rgb('+m.color.join(',')+');width:'+100/colors.length+'%', cls: 'color-preview'},
'L: '+Math.floor(m.hsv[0]),D.h('br'),
'A: '+Math.round(m.hsv[1]), D.h('br'),
'B: '+Math.round(m.hsv[2])
);
})
);
loadJSONcollection(partName, colors);
return;
}
return D.div({cls: 'colors'},
colors.map(function(m){
return D.span({style: 'background:rgb('+m.color.join(',')+');', cls: 'color-preview'},
'H: '+Math.floor(m.hsv[0]*360),D.h('br'),
'L: '+Math.floor(m.hsv[0]),D.h('br'),
'A: '+Math.round(m.hsv[1]), D.h('br'),
'B: '+Math.round(m.hsv[2])
/*'H: '+Math.floor(m.hsv[0]*360),D.h('br'),
'S: '+Math.round(m.hsv[1]*100)+'%', D.h('br'),
'V: '+Math.round(m.hsv[2]*100)+'%'
'V: '+Math.round(m.hsv[2]*100)+'%'*/
);
}),
function(draw) {
getData(partName, function(res){
var b = store.sub( [ 'threshold', 'maxCount' ], function( threshold, maxCount ){
var b = store.sub( [ 'threshold', 'maxCount', 'maxThreshold' ], function( threshold, maxCount, maxThreshold ){
var list = [];
res.products.forEach( function( product ){
product.variants.forEach( function( variant ){
var color = variant.option2.substr( 1 ),
r = parseInt( color[ 0 ] + color[ 1 ], 16 ),
g = parseInt( color[ 2 ] + color[ 3 ], 16 ),
b = parseInt( color[ 4 ] + color[ 4 ], 16 );
r = parseInt( color[ 0 ] + color[ 1 ], 16 ),
g = parseInt( color[ 2 ] + color[ 3 ], 16 ),
b = parseInt( color[ 4 ] + color[ 4 ], 16 );
list.push( {
pid: product.id,
......@@ -256,7 +521,7 @@ var extractor = function(partName, ctx, from, k, debug) {
colorName: variant.option1,
img: variant.featured_image && variant.featured_image.src || ( product.images[ 0 ] || {} ).src,
sku: variant.option3,
hsv: rgbToHsv( r, g, b )
hsv: rgbToLab( r, g, b )
} );
} );
} );
......@@ -265,28 +530,43 @@ var extractor = function(partName, ctx, from, k, debug) {
colors.map( function( clr ){
var epsilon = threshold;
var matched = list.map( function( i ){
return { i: i, d: hsvDistance( i.hsv, clr.hsv ), c: clr };
return { i: i, d: labDistance( i.hsv, clr.hsv ), c: clr };
} )
.filter( function( i ){
return Math.max( 0, Math.min( 100, ( 1 - i.d*3 ) * 100 ) ) >= threshold;
return i.d <= maxThreshold;
} )
.sort( function( a, b ){
return a.d - b.d;
} );
var allMatched = matched.slice();
if(maxCount === 0)
return matched;
matched = allMatched.filter(function(a) {
return a.d <= threshold;
});
var lastMatched = matched;
while( matched.length > maxCount ){
epsilon = 100-(100-epsilon) / 3 * 2;
var m = 0, step = 0;
while( matched.length < maxCount ){
step++;
epsilon = epsilon + 0.25;
matched = matched.filter( function( i ){
return Math.max( 0, Math.min( 100, ( 1 - i.d*3 ) * 100 ) ) >= epsilon;
} );
if( matched.length === 0 ){
matched = lastMatched.slice(0,maxCount);
if(epsilon > maxThreshold)
break;
if(m*step/5+epsilon>maxThreshold)
break;
if(matched.length && epsilon>(threshold+maxThreshold)/2)
break;
}
matched = allMatched.filter( function( i ){
return i.d <= epsilon;
} );
m = Math.max(m, matched.length);
lastMatched = matched;
}
/*if(!matched.length>3){
......@@ -298,7 +578,7 @@ var extractor = function(partName, ctx, from, k, debug) {
}else{
return matched.slice(0, 2);
}*/
return matched;
return matched.slice(0,maxCount);
} ) ).filter( function( item ){
if( !( item.i.vid in used ) ){
used[ item.i.vid ] = true;
......@@ -322,14 +602,14 @@ var extractor = function(partName, ctx, from, k, debug) {
D.div( { cls: 'cp2', style: { background: item.i.color } } ),
D.h( 'img', { cls: 'preview-img', src: item.i.img } ),
D.div( { cls: 'name' }, item.i.title ),
D.div( { cls: 'accuracy' }, ( Math.max( 0, Math.min( 100, ( 1 - item.d * 3 ) * 100 ) ) ).toFixed( 2 ) + '%' )
D.div( { cls: 'accuracy' }, (100-item.d).toFixed( 2 ) + '%' )
)
} )
)
) );
} );
console.log(b)
//console.log(b)
binds.push(b);
});
......
var store = new Store({
threshold: 75,
maxCount: 5
});
var TINY_FACE_DETECTOR = 1;var SSD_MOBILENETV1 = 2
function getCurrentFaceDetectionNet() {
selectedFaceDetector = TINY_FACE_DETECTOR
if (selectedFaceDetector === SSD_MOBILENETV1) {
return faceapi.nets.ssdMobilenetv1
}
if (selectedFaceDetector === TINY_FACE_DETECTOR) {
return faceapi.nets.tinyFaceDetector
}
}
function isFaceDetectionModelLoaded() {
return !!getCurrentFaceDetectionNet().params
}
window.s0 = 224;
var canvas = document.getElementById("chosen_img");
var cc = canvas.getContext("2d");
var detection = document.getElementById("detection");
var detectionCtx = detection.getContext("2d");
var overlay = document.getElementById("overlay");
var overlayCC = overlay.getContext("2d");
var canvas_container = document.getElementById("canvas_container");
var img = new Image();
img.onload = function() {
console.log(this, 'ghm');
// check if the picture is taken landscope.
if (img.width > img.height) {
// Change max width and max height depends on screen width.
if (screen < 414) {
// for Mobile
imgSizeFit(img, 256, 300);
} else {
imgSizeFit(img, 640, 480);
}
} else {
// Change max width and max height depends on screen width.
if (screen < 414) {
// for Mobile
imgSizeFit(img, 256, 450);
} else {
imgSizeFit(img, 480, 640);
}
}
cc.drawImage(img, 0, 0, canvas.width, canvas.height);
doMatching(canvas, cc);
};
// set Image src that user chose.
img.src = localStorage.getItem("image_data");
function imgSizeFit(img, maxWidth, maxHeight) {
var ratio = Math.min(
1,
maxWidth / img.naturalWidth,
maxHeight / img.naturalHeight
);
var neww = img.naturalWidth * ratio;
var newh = img.naturalHeight * ratio;
canvas_container.style.width = `${neww}px`;
canvas.setAttribute("width", neww);
canvas.setAttribute("height", newh);
overlay.setAttribute("width", neww);
overlay.setAttribute("height", newh);
detection.setAttribute("width", neww);
detection.setAttribute("height", newh);
}
async function doMatching(el, ctx) {
var inputSize = s0;
var scoreThreshold = 0.4;
if (!isFaceDetectionModelLoaded()){
await getCurrentFaceDetectionNet().load( assetDir );
await faceapi.loadFaceLandmarkModel(assetDir );
}
var options = new faceapi.TinyFaceDetectorOptions( { inputSize, scoreThreshold } );
var dims = {width:640, height: 480};
detectionCtx.clearRect(0,0,dims.width, dims.height);
if(img.width/dims.width*dims.height>img.height){
var h = img.height/(img.width/dims.width);
detectionCtx.drawImage(img, 0, (dims.height-h)/2, dims.width, h);
}else{
var w = img.width/(img.height/dims.height);
detectionCtx.drawImage(img, (dims.width-w)/2, 0, w, dims.height);
}
let result = await faceapi.detectSingleFace( detection, options ).withFaceLandmarks();//.withFaceDescriptor();
if(result){
var toPoint = function(p) {
return new Point(p.x, p.y)
};
var jaw = result.landmarks.getJawOutline(),
nose = result.landmarks.getNose(),
mouth = result.landmarks.getMouth(),
leftBrow = result.landmarks.getLeftEyeBrow(),
rightBrow = result.landmarks.getRightEyeBrow(),
leftEye = result.landmarks.getLeftEye(),
rightEye = result.landmarks.getRightEye();
var top = toPoint(nose[6]).lerp(nose[0], 2).y;
if(top<0){
var dtop = -top/dims.height;
detectionCtx.drawImage(img, dtop*dims.width/2, -top, dims.width-dtop*dims.width, dims.height+top);
detectionCtx.fillStyle = GetPixel(detectionCtx,4,4);
detectionCtx.fillRect(0,0,dims.width, -top);
result = await faceapi.detectSingleFace( detection, options ).withFaceLandmarks();//.withFaceDescriptor();
if(!result){
return setTimeout( f, 300 );
}
jaw = result.landmarks.getJawOutline(),
nose = result.landmarks.getNose(),
mouth = result.landmarks.getMouth(),
leftBrow = result.landmarks.getLeftEyeBrow(),
rightBrow = result.landmarks.getRightEyeBrow(),
leftEye = result.landmarks.getLeftEye(),
rightEye = result.landmarks.getRightEye();
}
overlayCC.clearRect(0,0,dims.width, dims.height);
faceapi.draw.drawFaceLandmarks( overlay, faceapi.resizeResults( result, dims ) );
overlayCC.fillStyle='blue'; mouth.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
overlayCC.fillStyle='red'; jaw.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
overlayCC.fillStyle='yellow'; leftEye.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
overlayCC.fillStyle='yellow'; rightEye.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
overlayCC.fillStyle='red'; leftBrow.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
overlayCC.fillStyle='red'; rightBrow.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
overlayCC.fillStyle='blue'; nose.forEach(function(p, i) { overlayCC.fillText(i+'', p.x, p.y) });
var faceMarks = new FaceMarks(result.landmarks);
var cheek = faceMarks.marks.cheek;
var ratio = (cheek.left.base[0].distance(cheek.left.base[1])/cheek.right.base[0].distance(cheek.right.base[1]));
var bad = 2;
var lips = [];
lips = lips.concat(faceMarks.marks.lips.top);
lips = lips.concat(faceMarks.marks.lips.bottom);
var browsSearchArea = [];
browsSearchArea.push([
toPoint(leftBrow[4]),
toPoint(leftEye[2]).middle(toPoint(leftBrow[4])),
toPoint(leftBrow[1])
]);
browsSearchArea.push([
toPoint(rightBrow[0]),
toPoint(rightEye[1]).middle(toPoint(rightBrow[0])),
toPoint(rightBrow[3])
]);
var eyes = [];
if(ratio > bad){
cheek = [cheek.left.base, cheek.left.bonus1, cheek.left.bonus2];
browsSearchArea.pop();
eyes = [
faceMarks.marks.eyes.left.t1,
faceMarks.marks.eyes.left.t2,
faceMarks.marks.eyes.left.t3,
faceMarks.marks.eyes.left.t4
];
}else if(ratio < 1/bad){
cheek = [cheek.right.base, cheek.right.bonus1, cheek.right.bonus2];
browsSearchArea.shift();
eyes = [
faceMarks.marks.eyes.right.t1,
faceMarks.marks.eyes.right.t2,
faceMarks.marks.eyes.right.t3,
faceMarks.marks.eyes.right.t4
];
}else{
cheek = [
cheek.left.base,
cheek.left.bonus1,
cheek.left.bonus2,
cheek.right.base,
cheek.right.bonus1,
cheek.right.bonus2
];
eyes = [
faceMarks.marks.eyes.left.t1,
faceMarks.marks.eyes.left.t2,
faceMarks.marks.eyes.left.t3,
faceMarks.marks.eyes.left.t4,
faceMarks.marks.eyes.right.t1,
faceMarks.marks.eyes.right.t2,
faceMarks.marks.eyes.right.t3,
faceMarks.marks.eyes.right.t4
];
}
var drawPoly = function(poly) {
overlayCC.beginPath();
for(var i = 0,_i = poly.length; i <= _i; i++ ){
if(i === 0){
overlayCC.moveTo( poly[ i ].x, poly[ i ].y );
}else{
overlayCC.lineTo( poly[ i % _i ].x, poly[ i % _i ].y );
}
}
overlayCC.stroke();
};
cheek.forEach(drawPoly);
lips.forEach(drawPoly);
browsSearchArea.forEach(drawPoly);
eyes.forEach(drawPoly);
//document.getElementById('ccolors').getContext('2d').clearRect(0,0,120,480);
extracting = -1;
extractor('lips', detectionCtx, lips, 5,1);
extractor('blush', detectionCtx, cheek, 3,1);
extractor('brows', detectionCtx, browsSearchArea, 2,1);
extractor('eyes', detectionCtx, eyes, 6, 1);
}
document.getElementById("loading").classList.add("d-none"); // make loading gif disappear
document.getElementById("color_matching_products-container").style.display = 'block';
}
function matchingProductsFormat(product, index) {
// If the variants doesn't have Image src
var out_put = "";
out_put = `<div id="color_matching_${product.pid}" class="grid__item grid-product small--one-half cd-medium-up--one-third grid-product__has-quick-shop aos-init aos-animate" data-aos="row-of-3">
<div class="grid-product__content">
<a class="grid-product__link " href="/products/${product.p.handle}" target="_blank">
<div class="grid-product__image-mask">
<div class="grid__image-ratio grid__image-ratio--portrait lazyloaded" data-bgset="${product.img}" style="background-image: url(${product.img});"">
</div>
</div>
<div class="grid-product__meta">
<div class="grid-product__title grid-product__title--body">${product.title}</div>
<div class="grid-product__vendor">${product.p.vendor}</div>
<div class="grid-product__price">$${product.v.price}
</div>
</div>
</a>
<!-- ============= add custom button ( quick view, love, add to cart ) =============-->
<div class="quick-product__menus quick-product__btn cssgrid w-100">
<!-- add wishlist -->
<div class="d-flex quick-product__icon-bg">
<div class="ssw-faveiticon sswfaveicon${product.pid}">
<i data-product-id="${product.pid}" data-count="0" class="ssw-icon-heart-o ssw-fave-icon ssw-wishlist-element ssw-not-synch" title="{{ 'socialshopwave.fave' | t }}" data-params="{'product_id':'${product.pid}','event':'fave_button','page':'product_profile'}"></i>
<span class="faves-count">...</span>
</div>
</div>
<!-- end of add wishlist -->
<div aria-expanded class="quick-product__btn js-modal-open-quick-modal-${product.pid} d-flex quick-product__icon-bg quick-product__view" data-product-id="${product.pid}">
<a href="/products/${product.p.handle}" target="_blank" class="w-100 h-100">
<span class="quick-product__label d-flex align-self-center quick-try-on w-100 h-100"></span>
</a>
</div>
<form action="/cart/add" class="d-flex quick-product__icon-bg" id="product-form-${product.pid}" method="post">
<input name="id" type="hidden" value="${product.vid}">
<div>
<button class="quick_add_cart_icon d-flex quick-product__icon-bg" name="add" type="submit">
<span class="quick_add_cart_icon_span"></span>
</button>
</div>
</form>
</div>
<!-- ============= end of custom button =============-->
</div>
<div class="grid-product__colors grid-product__colors--${product.pid}">
<div id="color_squares_container-${product.pid}" class="d-flex">
</div>
</div>
</div>
</div>`;
return out_put;
}
function colorSquareFormat(product, color, showSameColorName) {
var output = "";
var count = 0;
var LIMIT_SHOWING_COLOR_SWATCHES = 5;
for (let i = 0; i < product.variants.length; i++) {
const variant = product.variants[i];
const option2_value = variant.option2;
const option2_name = closest_color(option2_value, showSameColorName);
if (typeof color != "string") {
for (let i = 0; i < color.length; i++) {
const eye_cl = color[i];
if (eye_cl == option2_name) {
count++;
if (count < LIMIT_SHOWING_COLOR_SWATCHES) {
output += `<button class="color-swatch color-swatch--small color-swatch--cabbage-rose color-swatch--with-image" data-variant-id="${variant.id}" style="background-color: ${option2_value};"></button>`;
}
}
}
} else {
if (color == option2_name) {
count++;
if (count < LIMIT_SHOWING_COLOR_SWATCHES) {
output += `<button class="color-swatch color-swatch--small color-swatch--cabbage-rose color-swatch--with-image" data-variant-id="${variant.id}" style="background-color: ${option2_value};"></button>`;
}
}
}
}
if (count >= LIMIT_SHOWING_COLOR_SWATCHES) {
output += ` +${count - LIMIT_SHOWING_COLOR_SWATCHES + 1}`;
}
return output;
}
function miloAnswer(bool) {
var milo_answer = "";
if (bool) {
milo_answer =
"Here are products matching the colours that you are looking for. Try them on!";
document.getElementById("milo-answer-text").innerText = milo_answer;
} else {
milo_answer = `<button class="bg-yellow option_btn"><a href="/pages/color-detection" class="text-white">Choose another file</a></button>`;
document.getElementById("milo-answer-text").innerHTML = milo_answer;
document.getElementById("color_results_holder").classList.add("d-none");
}
}
function loadJSONcollection(fpart, colors) {//}, html_cl, showSameColorName) {
var xmlhttp = new XMLHttpRequest();
var url = `/collections/cd-${fpart}-results/products.json?limit=250?page=1`;
xmlhttp.open("GET", url, true);
try {
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var products = JSON.parse(this.responseText);
colorMatchProductChecker(products, fpart, colors);
miloAnswer(true);
}
};
} catch (err) {
// instead of onerror
alert("Ajax Error. Please check your Internet connection.");
}
function colorMatchProductChecker(res, fp, colors) {
var threshold = 75, maxCount = 5;
var list = [];
res.products.forEach( function( product ){
product.variants.forEach( function( variant ){
var color = variant.option2.substr( 1 ),
r = parseInt( color[ 0 ] + color[ 1 ], 16 ),
g = parseInt( color[ 2 ] + color[ 3 ], 16 ),
b = parseInt( color[ 4 ] + color[ 4 ], 16 );
list.push( {
v: variant,
p: product,
pid: product.id,
vid: variant.id,
description: product.body_html,
title: product.title,
color: variant.option2,
colorName: variant.option1,
img: variant.featured_image && variant.featured_image.src || ( product.images[ 0 ] || {} ).src || "https://cdn.shopify.com/s/files/1/0275/8036/6900/files/2_Main_Vertical_Transparent.png?v=1575867010",
sku: variant.option3,
hsv: rgbToHsv( r, g, b )
} );
} );
} );
var used = {};
var all = [].concat.apply( [],
colors.map( function( clr ){
var epsilon = threshold;
var matched = list.map( function( i ){
return { i: i, d: hsvDistance( i.hsv, clr.hsv ), c: clr };
} )
.filter( function( i ){
return Math.max( 0, Math.min( 100, ( 1 - i.d*3 ) * 100 ) ) >= threshold;
} )
.sort( function( a, b ){
return a.d - b.d;
} );
if(maxCount === 0)
return matched;
var lastMatched = matched;
while( matched.length > maxCount ){
epsilon = 100-(100-epsilon) / 3 * 2;
matched = matched.filter( function( i ){
return Math.max( 0, Math.min( 100, ( 1 - i.d*3 ) * 100 ) ) >= epsilon;
} );
if( matched.length === 0 ){
matched = lastMatched.slice(0,maxCount);
break;
}
lastMatched = matched;
}
/*if(!matched.length>3){
matched = matched.f;
return matched;
}*/
/*if(matched[0].d<0.02){
return matched.slice( 0, 1 )
}else{
return matched.slice(0, 2);
}*/
return matched;
} ) ).filter( function( item ){
if( !( item.i.vid in used ) ){
used[ item.i.vid ] = true;
return true;
}
return false;
} );
var count = all.length;
all.map( function( item ){
document
.getElementById(`${fp}-result`)
.insertAdjacentHTML(
"beforeend",
matchingProductsFormat(item.i)
);
/*document.getElementById(
`color_squares_container-${product.id}`
).innerHTML = colorSquareFormat(
product,
cl,
showSameColorName
);*/
} );
/*
var count = 0;
// reset
document.getElementById(`${fp}-result`).innerHTML = "";
for (let product_i = 0; product_i < data.products.length; product_i++) {
const product = data.products[product_i];
for (
let variant_i = 0;
variant_i < product.variants.length;
variant_i++
) {
const variant = product.variants[variant_i];
const option2_value = variant.option2;
const option2_name = closest_color(
option2_value,
showSameColorName
);
// if eye's color arr
if (typeof cl != "string") {
for (let cl_i = 0; cl_i < cl.length; cl_i++) {
const eye_cl = cl[cl_i];
if (eye_cl == option2_name) {
count++;
document
.getElementById(`${fp}-result`)
.insertAdjacentHTML(
"beforeend",
matchingProductsFormat(product, variant_i)
);
document.getElementById(
`color_squares_container-${product.id}`
).innerHTML = colorSquareFormat(
product,
cl,
showSameColorName
);
cl_i = cl.length - 1;
variant_i = product.variants.length - 1;
}
}
} else {
if (cl == option2_name) {
count++;
document
.getElementById(`${fp}-result`)
.insertAdjacentHTML(
"beforeend",
matchingProductsFormat(product, variant_i)
);
document.getElementById(
`color_squares_container-${product.id}`
).innerHTML = colorSquareFormat(
product,
cl,
showSameColorName
);
variant_i = product.variants.length - 1;
}
}
}
}*/
var showSameColorName = true;
var option_button_out = "";
var product_output = "";
// No matching products
json_FaceDetectionResults = count;
if (count === 0) {
product_output +=
"<span style='color: darkgrey;'>no match...</span>";
document.getElementById(`${fp}-result`).innerHTML = product_output;
} else if (showSameColorName) {
// show similar or close color products
option_button_out += `<p class="milo-text text-center">Do you want to see products of close colours?
<br>The may suit you better.</p>
<div class="d-flex jc-sb">
<button class="text-white bg-yellow option_btn" onclick="setJSON(false)">Yes, show me.</button>
<button class="bg-black option_btn"><a href="/pages/color-detection" class="text-white">No, go back.</a></button>
</div>`;
document.getElementById(
"cd_option_button_container"
).innerHTML = option_button_out;
} else if (!showSameColorName) {
// back to same color products result or back to initial page.
option_button_out += `<div class="d-flex jc-sb">
<button class="text-white bg-yellow option_btn" onclick="setJSON(true)">Back</button>
<button class="bg-black option_btn"><a href="/pages/color-detection" class="text-white">Back to Miro</a></button>
</div>`;
document.getElementById(
"cd_option_button_container"
).innerHTML = option_button_out;
} else {
return;
}
}
}
var json_FaceDetectionResults = false;
function setJSON(bool) {
document.getElementById("loading").classList.add("d-none"); // make loading gif disappear
document.getElementById("color_results_holder").classList.remove("d-none");
// if the face detection success
if (json_FaceDetectionResults) {
//setResultsAndJSON(json_FaceDetectionResults, bool);
document.getElementById("color_matching_products-container").style =
"block";
} else {
// if face detection failed,
document.getElementById("color_results_holder").classList.add("d-none");
}
}
\ No newline at end of file
......@@ -128,8 +128,9 @@
</div>
<script>
var store = new Store({
threshold: 60,
maxCount: 5
threshold: 0.25,
maxCount: 5,
maxThreshold: 15
});
......@@ -409,13 +410,12 @@ var videoEl = inputVideo;
),
block(
'Eyes',
extractor('eyes', ctx2, eyes, 6, 1)
extractor('eyes', ctx2, eyes, 6)
)
]);
//ctx2.putImageData(imageData, minX, minY);
// debugger;
......@@ -474,6 +474,9 @@ var videoEl = inputVideo;
),
D.h('label', {},
'Threshold: ', D.h(Input, {bind: store.bind('threshold')})
),
D.h('label', {},
'maxThreshold: ', D.h(Input, {bind: store.bind('maxThreshold')})
)
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment