Permalink
Browse files

add positive collection cropping code

  • Loading branch information...
1 parent 7108206 commit 5eed653446d0183fdacaba6ecb0ec4c7b1938664 @harthur committed Jun 27, 2012
Showing with 174 additions and 111 deletions.
  1. +11 −7 README.md
  2. +0 −19 features.js
  3. +1 −1 testing/test.js
  4. +0 −1 training/collection/{makenegs.js → make-negs.js}
  5. +156 −0 training/collection/make-positives.js
  6. +0 −31 training/collection/makepos.js
  7. +6 −52 utils.js
View
@@ -14,7 +14,7 @@ console.log(cats[0]);
[Kittydar demo](http://harthur.github.com/kittydar)
-# Install
+## Install
For node:
@@ -24,26 +24,30 @@ npm install kittydar
Or grab the [browser file](http://github.com/harthur/kittydar/downloads)
-# Specifics
+## Specifics
Kittydar takes a `canvas` element. In node you can get a `Canvas` object with [node-canvas](https://github.com/LearnBoost/node-canvas).
Kittydar will give an approximate rectangle around the cat's head. Each rectangle has an `x` and `y` for the top left corner, and a `width` and `height` of the rectangle.
-# How it works
+## How it works
Kittydar first chops the image up into many "windows" to test for the presence of a cat head. For each window, kittydar first extracts more tractable data from the image's data. Namely, it computes the [Histogram of Orient Gradients](http://en.wikipedia.org/wiki/Histogram_of_oriented_gradients) descriptor of the image, using the [hog-descriptor](http://github.com/harthur/hog-descriptor) library. This data describes the directions of the edges in the image (where the image changes from light to dark and vice versa) and what strength they are. This data is a vector of numbers that is then fed into a [neural network](https://github.com/harthur/brain) which gives a number from `0` to `1` on how likely the histogram data represents a cat.
The neural network (the JSON of which is located in this repo) has been pre-trained with thousands of photos of cat heads and their histograms, as well as thousands of non-cats. See the repo for the node training scripts.
-# Limitations
+## Limitations
Kittydar will miss cats sometimes, and sometimes classify non-cats as cats. It's best at detecting upright cats that are facing forward, but it can handle a small tilt or turn in the head.
-Kittydar isn't fast. It'll take a few seconds to find the cats in one image. There's lots of room for improvement, so fork and send requests.
+Kittydar isn't fast. It'll take a few seconds to find the cats in one image.
-### Propers
+There's lots of room for improvement, so fork and send requests.
+
+## Propers
* This informative reasearch paper: [Cat Head Detection - How to Effectively Exploit Shape and Texture Features](http://research.microsoft.com/pubs/80582/ECCV_CAT_PROC.pdf) by Weiwei Zhang, Jian Sun, and Xiaoou Tang.
-* This off the hook [dataset of cat images](http://137.189.35.203/WebUI/CatDatabase/catData.html) annotated with the locations of the cat heads.
+
+* This off the hook [dataset of cat images](http://137.189.35.203/WebUI/CatDatabase/catData.html) annotated with the locations of the cat's ears, eyes, and mouth.
+
* [@gdeglin](http://github.com/gdeglin) for the name.
View
@@ -1,19 +0,0 @@
-var hog = require("hog-descriptor"),
- utils = require("./utils");
-
-var defaultParams = {
- "cellSize": 6,
- "blockSize": 2,
- "blockStride": 1,
- "bins": 6,
- "norm": "L2"
-}
-
-var size = 48;
-
-exports.extractFeatures = function(canvas, params) {
- canvas = utils.resizeCanvas(canvas, size, size);
-
- var descriptor = hog.extractHOG(canvas, params || defaultParams);
- return descriptor;
-}
View
@@ -20,7 +20,7 @@ function runTest() {
if (err) throw err;
var images = files.filter(function(file) {
- return path.extname(file) == ".jpg";
+ return path.extname(file) == ".png";
})
async.forEach(images, function(file, done) {
@@ -1,7 +1,6 @@
var http = require("http"),
url = require("url"),
fs = require("fs"),
- async = require("async"),
path = require("path"),
Canvas = require("canvas"),
_ = require("underscore"),
@@ -0,0 +1,156 @@
+var fs = require("fs"),
+ path = require("path"),
+ async = require("async"),
+ nomnom = require("nomnom"),
+ Canvas = require("canvas"),
+ cropper = require("./cropper"),
+ utils = require("../../utils");
+
+var opts = nomnom.options({
+ indir: {
+ position: 0,
+ default: __dirname + "/CAT_DATASET/",
+ help: "Directory of cat pics from http://137.189.35.203/WebUI/CatDatabase/catData.html"
+ },
+ outdir: {
+ position: 1,
+ default: __dirname + "/POSITIVES/",
+ help: "Directory to save rotated and cropped cat face images"
+ }
+}).colors().parse()
+
+// Crop the cat head from each photo in cat dataset.
+// Everything is sync to avoid OS fd limit
+fs.readdir(opts.indir, function(err, files) {
+ if (err) throw err;
+
+ var images = files.filter(function(file) {
+ return path.extname(file) == ".jpg";
+ })
+
+ console.log(images.length, "cat images");
+
+ console.time("cropping");
+
+ for (var i = 0; i < images.length; i++) {
+ var file = images[i];
+ var infile = opts.indir + "/" + file;
+ var outfile = opts.outdir + "/" + path.basename(file, ".jpg") + ".png";
+
+ cropFace(infile, outfile);
+
+ if (i % 50 == 0) {
+ console.log(i);
+ }
+ }
+
+ console.timeEnd("cropping");
+});
+
+function cropFace(file, outfile) {
+ var canvas = utils.drawImgToCanvasSync(file);
+
+ var catfile = file + ".cat";
+ var annotations = getCatDataSync(catfile);
+
+ var processed = transformCanvas(canvas, annotations, file);
+
+ utils.writeCanvasToFileSync(processed, outfile);
+}
+
+function getCatDataSync(file, callback) {
+ // read special annotation file from cat dataset
+ var text = fs.readFileSync(file, "utf-8");
+
+ var vals = text.split(" ").map(parseFloat);
+ var length = vals[0];
+ if (length != 9) {
+ console.log("different number of points:", length);
+ }
+
+ // locations of ears, eyes, and mouth
+ var features = ["leye", "reye", "mouth", "lear1", "lear2",
+ "lear3", "rear1", "rear2", "rear3"];
+ var points = {};
+ for (var i = 0; i < length; i ++) {
+ points[features[i]] = {
+ x: vals[i * 2 + 1],
+ y: vals[i * 2 + 2]
+ }
+ }
+
+ return points;
+}
+
+function transformCanvas(canvas, points, file) {
+ // Rotate and crop according to the shape detector training specifications in
+ // "Cat Head Detection - How to Effectively Exploit Shape and Texture Features"
+ // http://research.microsoft.com/pubs/80582/ECCV_CAT_PROC.pdf
+
+ // if cat's head is turned more than 90deg
+ var flipped = points.lear2.x > points.rear2.x;
+
+ // find angle the face is tilted at
+ var opp = points.rear2.y - points.lear2.y;
+ var adj = points.rear2.x - points.lear2.x;
+ if (flipped) {
+ adj = -adj;
+ }
+ var hyp = Math.sqrt(Math.pow(opp, 2) + Math.pow(adj, 2));
+
+ var angle = -Math.atan(opp / adj);
+
+ var rotation = angle;
+ if (flipped) {
+ if (angle < 0) {
+ rotation = Math.PI - angle;
+ }
+ else {
+ rotation = Math.PI - angle;
+ }
+ }
+
+ var length = 4/3 * hyp; // length of final square canvas
+ var drop = (5/12 * length); // len from ears to center of face
+
+ // make a new canvas that can fit rotated image
+ var dim = canvas.width + canvas.height;
+ var transCanvas = new Canvas(dim, dim);
+ var ctx = transCanvas.getContext("2d");
+
+ // will rotate on draw so tips of cat's ears are horizontal
+ var half = dim / 2;
+ ctx.translate(half, half);
+ ctx.rotate(rotation);
+ ctx.translate(-half, -half)
+
+ var longOpp = Math.cos(angle) * drop;
+ var longAdj = Math.sin(angle) * drop;
+
+ var addX, addY;
+
+ if (flipped) {
+ addX = longAdj -(adj / 2);
+ addY = -longOpp -(opp / 2);
+ }
+ else {
+ addX = longAdj + (adj / 2);
+ addY = longOpp - (opp / 2);
+ }
+
+ var centerX = points.lear2.x + addX;
+ var centerY = points.rear2.y + addY;
+
+ // draw image so center of cat's face is in the center of canvas
+ ctx.drawImage(canvas, half - centerX, half - centerY);
+
+ var cropCanvas = new Canvas(length, length);
+ ctx = cropCanvas.getContext("2d");
+
+ // crop and resize cat face
+ var dim = hyp / 0.75;
+ ctx.drawImage(transCanvas, half - (dim / 2), half - (dim / 2),
+ dim, dim, 0, 0, length, length);
+
+ return cropCanvas;
+}
@@ -1,31 +0,0 @@
-var fs = require("fs"),
- path = require("path"),
- async = require("async"),
- cropper = require("./cropper");
-
-var dir = __dirname + "/CATS_ORIG/CAT_06/";
-var outdir = __dirname + "/POS_RAW/";
-
-var count = 0;
-var start = 8622;
-
-fs.readdir(dir, function(err, files) {
- if (err) throw err;
-
- console.log("file count", files.length / 2);
-
- var images = files.filter(function(file) {
- return path.extname(file) == ".jpg";
- })
-
- async.forEachSeries(images, function(file, done) {
- count++;
- var outfile = outdir + (start + count) + ".jpg";
-
- cropper.makeCatFaceImg(dir + file, outfile, function() {
- console.log("saved cat face to:", outfile);
- setTimeout(done, 100);
- });
- });
-})
-
View
@@ -3,29 +3,6 @@ var url = require("url"),
http = require("http"),
Canvas = require("canvas");
-exports.getCatData = function(file, callback) {
- fs.readFile(file, "utf-8", function(err, data) {
- if (err) throw err;
-
- var vals = data.split(" ").map(parseFloat);
- var length = vals[0];
- if (length != 9) {
- console.log("different number of points:", length);
- }
-
- var features = ["leye", "reye", "mouth", "lear1", "lear2",
- "lear3", "rear1", "rear2", "rear3"];
- var points = {};
- for (var i = 0; i < length; i ++) {
- points[features[i]] = {
- x: vals[i * 2 + 1],
- y: vals[i * 2 + 2]
- }
- }
- callback(points);
- })
-}
-
exports.dataToCanvas = function(imagedata) {
img = new Canvas.Image();
img.src = new Buffer(imagedata, 'binary');
@@ -42,13 +19,11 @@ exports.dataToCanvas = function(imagedata) {
exports.drawImgToCanvas = function(file, callback) {
fs.readFile(file, function(err, data) {
if (err) {
- // console.log(file, err)
return callback(err);
}
try {
var canvas = exports.dataToCanvas(data);
} catch(err) {
- // console.log(file, err)
return callback(err);
}
callback(null, canvas);
@@ -62,39 +37,18 @@ exports.drawImgToCanvasSync = function(file) {
}
exports.writeCanvasToFile = function(canvas, file, callback) {
- var out = fs.createWriteStream(file)
- var stream = canvas.createJPEGStream();
-
- stream.on('data', function(chunk) {
- out.write(chunk);
- });
+ var buffer = canvas.toBuffer(); // png data
+ fs.writeFile(file, buffer, callback);
+}
- stream.on('end', function() {
- callback();
- });
+exports.writeCanvasToFileSync = function(canvas, file) {
+ var buffer = canvas.toBuffer(); // png data
+ fs.writeFileSync(file, buffer);
}
exports.saveImgToFile = function(file, imagedata) {
fs.writeFile(file, imagedata, 'binary', function(err) {
if (err) throw err;
- console.log("File " + file + " saved");
- })
-}
-
-exports.getImg = function(uri, callback) {
- var options = url.parse(uri);
-
- http.get(options, function(res) {
- var imagedata = "";
- res.setEncoding('binary');
-
- res.on('data', function(chunk) {
- imagedata += chunk
- });
-
- res.on('end', function() {
- callback(imagedata);
- })
})
}

0 comments on commit 5eed653

Please sign in to comment.