1/** 2 * SIFT: scale-invariant-feature-transform, keypoints 3 * 4 * @author toolpic.com 5 */ 6 7/* global HELPER, IMAGE, ImageFilters, LAYER, canvas_active */ 8 9var SIFT = new SIFT_CLASS(); 10 11function SIFT_CLASS() { 12 /** 13 * contrast check, smaller - more points, better accuracy, but slower 14 */ 15 var avg_offset = 50; 16 17 /** 18 * how much pixels to check for each side to get average 19 */ 20 var avg_step = 4; 21 22 //generate key points for image 23 this.generate_keypoints = function (canvas, show_points) { 24 var W = canvas.width; 25 var H = canvas.height; 26 HELPER.timer_init(); 27 28 //check whitespace 29 var trim_details = IMAGE.trim_info(canvas); 30 W = W - trim_details.left - trim_details.right; 31 H = H - trim_details.top - trim_details.bottom; 32 //make copy 33 var clone = document.createElement("canvas"); 34 clone.width = W; 35 clone.height = H; 36 var ctx = clone.getContext("2d"); 37 ctx.drawImage(canvas, -trim_details.left, -trim_details.top, canvas.width, canvas.height); 38 39 //greyscale 40 var imageData = ctx.getImageData(0, 0, W, H); 41 var filtered = ImageFilters.GrayScale(imageData); //add effect 42 ctx.putImageData(filtered, 0, 0); 43 44 //make few copies and blur each 45 var n = 5; 46 var copies = []; 47 for (var i = 0; i < n; i++) { 48 var tmp_canvas = document.createElement("canvas"); 49 tmp_canvas.width = W; 50 tmp_canvas.height = H; 51 var ctx_i = tmp_canvas.getContext("2d"); 52 ctx_i.drawImage(clone, 0, 0); 53 54 //gausian blur 55 var imageData = ctx_i.getImageData(0, 0, W, H); 56 var filtered = ImageFilters.GaussianBlur(imageData, i + 0.5); //add effect 57 ctx_i.putImageData(filtered, 0, 0); 58 59 copies.push(tmp_canvas); 60 } 61 62 //find extreme points 63 var points = []; 64 var n0 = avg_step * 2 + 1; 65 for (var c = 1; c < copies.length - 1; c++) { 66 var imageData = copies[c].getContext("2d").getImageData(0, 0, W, H).data; 67 var imageData0 = copies[c - 1].getContext("2d").getImageData(0, 0, W, H).data; 68 var imageData2 = copies[c + 1].getContext("2d").getImageData(0, 0, W, H).data; 69 for (var j = avg_step; j < H - avg_step; j++) { 70 for (var i = avg_step; i < W - avg_step; i++) { 71 var x = (i + j * W) * 4; 72 if (imageData[x + 3] == 0) 73 continue; //transparent 74 if (imageData[x] < imageData[x - 4] || imageData[x] < imageData[x + 4] || imageData[x] > imageData[x - 4] || imageData[x] > imageData[x + 4]) { 75 var x_pre = (i + (j - 1) * W) * 4; 76 var x_post = (i + (j + 1) * W) * 4; 77 //calc average 78 var area_average = 0; 79 for (var l = -avg_step; l <= avg_step; l++) { 80 var avgi = (i + (j - l) * W) * 4; 81 for (var a = -avg_step; a <= avg_step; a++) { 82 area_average += imageData[avgi + 4 * a]; 83 } 84 } 85 area_average = area_average / (n0 * n0); 86 //max 87 if (imageData[x] + avg_offset < area_average) { 88 var min = Math.min(imageData[x_pre - 4], imageData[x_pre], imageData[x_pre + 4], imageData[x - 4], imageData[x + 4], imageData[x_post - 4], imageData[x_post], imageData[x_post + 4]); 89 if (imageData[x] <= min) { 90 var min0 = Math.min(imageData0[x_pre - 4], imageData0[x_pre], imageData0[x_pre + 4], imageData0[x - 4], imageData0[x + 4], imageData0[x_post - 4], imageData0[x_post], imageData0[x_post + 4]); 91 if (imageData[x] <= min0) { 92 var min2 = Math.min(imageData2[x_pre - 4], imageData2[x_pre], imageData2[x_pre + 4], imageData2[x - 4], imageData2[x + 4], imageData2[x_post - 4], imageData2[x_post], imageData2[x_post + 4]); 93 if (imageData[x] <= min2) 94 points.push({ 95 x: i + trim_details.left, 96 y: j + trim_details.top, 97 w: Math.round(area_average - imageData[x] - avg_offset) 98 }); 99 } 100 } 101 continue; 102 } 103 //min 104 if (imageData[x] - avg_offset > area_average) { 105 var max = Math.max(imageData[x_pre - 4], imageData[x_pre], imageData[x_pre + 4], imageData[x - 4], imageData[x + 4], imageData[x_post - 4], imageData[x_post], imageData[x_post + 4]); 106 if (imageData[x] >= max) { 107 var max0 = Math.max(imageData0[x_pre - 4], imageData0[x_pre], imageData0[x_pre + 4], imageData0[x - 4], imageData0[x + 4], imageData0[x_post - 4], imageData0[x_post], imageData0[x_post + 4]); 108 if (imageData[x] >= max0) { 109 var max2 = Math.max(imageData2[x_pre - 4], imageData2[x_pre], imageData2[x_pre + 4], imageData2[x - 4], imageData2[x + 4], imageData2[x_post - 4], imageData2[x_post], imageData2[x_post + 4]); 110 if (imageData[x] >= max2){ 111 points.push({ 112 x: i + trim_details.left, 113 y: j + trim_details.top, 114 w: Math.round(imageData[x] - area_average - avg_offset) 115 });
116 } 117 } 118 } 119 } 120 } 121 } 122 } 123 } 124 //make unique 125 for (var i = 0; i < points.length; i++) { 126 for (var j = 0; j < points.length; j++) { 127 if (i != j && points[i].x == points[j].x && points[i].y == points[j].y) { 128 points.splice(i, 1); 129 i--; 130 break; 131 } 132 } 133 } 134 135 //show points? 136 if (show_points === true) { 137 var time = HELPER.timer('', true); 138 console.log('key points: ' + points.length + ", " + time); 139 LAYER.layer_add(); 140 141 var size = 3; 142 canvas_active().fillStyle = "#ff0000"; 143 for (var i in points) { 144 var point = points[i]; 145 canvas_active().beginPath(); 146 canvas_active().rect(point.x - Math.floor(size / 2) + 1, point.y - Math.floor(size / 2) + 1, size, size); 147 canvas_active().fill(); 148 } 149 } 150 else { 151 //sort by weights 152 points.sort(function (a, b) { 153 return parseFloat(b.w) - parseFloat(a.w); 154 }); 155 return { 156 points: points, 157 trim_details: trim_details 158 }; 159 } 160 }; 161 162 //returns average value of requested area from greyscale image 163 //area = {x, y, w, h} 164 this.get_area_average = function (area, imageData, i, j, size) { 165 var imgData = imageData.data; 166 var sum = 0; 167 var n = 0; 168 size = size / 100; //prepare to use 1-100% values 169 var stop_x = i + Math.round(size * area.x) + Math.round(size * area.w); 170 var stop_y = j + Math.round(size * area.y) + Math.round(size * area.h); 171 var img_width4 = imageData.width * 4; 172 var k0, k; 173 for (var y = j + Math.round(size * area.y); y < stop_y; y++) { 174 k0 = y * img_width4; 175 for (var x = i + Math.round(size * area.x); x < stop_x; x++) { 176 k = k0 + (x * 4); 177 sum = sum + imgData[k]; 178 n++; 179 } 180 } 181 return Math.round(sum / n); 182 }; 183}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.