diff --git a/README.md b/README.md index 4444cb04a0..da0710c964 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This repository holds Node.js samples used throughout [cloud.google.com](). * [Google Cloud Logging](#google-cloud-logging) * [Google Cloud Pub/Sub](#google-cloud-pubsub) * [Google Cloud Storage](#google-cloud-storage) +* [Google Cloud Vision](#google-cloud-vision) * [Google Prediction API](#google-prediction-api) * [Other Example Apps](#other-example-apps) * [More Information](#more-information) @@ -106,6 +107,10 @@ __Other Examples__ - Auth sample - [Source code][storage_1] | [Documentation][storage_2] +## Google Cloud Vision + +- Face detection - [Source code][vision_1] | [Documentation][vision_2] + ## Google Prediction API - Hosted Models sample - [Source code][predictionapi_1] | [Documentation][predictionapi_2] @@ -307,6 +312,9 @@ See [LICENSE](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/ma [storage_1]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/storage/authSample.js [storage_2]: https://cloud.google.com/storage/docs/authentication#acd-examples +[vision_1]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/vision/faceDetection.js +[vision_2]: https://cloud.google.com/vision/docs/face-tutorial + [predictionapi_1]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/prediction/hostedmodels.js [predictionapi_2]: https://cloud.google.com/prediction/docs/developer-guide#predictionfromappengine diff --git a/package.json b/package.json index cf0b74d502..3e62c5a330 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,15 @@ "deps_storage": "cd storage; npm i; cd ../", "deps_prediction": "cd prediction; npm i; cd ../", "deps_logging": "cd logging; npm i; cd ../", + "deps_vision": "cd vision; npm i; cd ../", "deps_functions": "cd functions/uuid; npm i; cd ../..", "pretest_geddy": "cd appengine/geddy; npm i geddy; GEDDY_SECRET=config/secrets.json; [[ -f $GEDDY_SECRET ]] || echo '{}' > $GEDDY_SECRET && node node_modules/.bin/geddy gen secret; cd ../..;", - "pretest": "npm run deps_gce; npm run deps_bigquery; npm run deps_datastore; npm run deps_monitoring; npm run deps_storage; npm run deps_pubsub; npm run deps_prediction; npm run deps_logging; npm run deps_functions; npm run pretest_geddy", + "pretest": "npm run deps_vision; npm run deps_gce; npm run deps_bigquery; npm run deps_datastore; npm run deps_monitoring; npm run deps_storage; npm run deps_pubsub; npm run deps_prediction; npm run deps_logging; npm run deps_functions; npm run pretest_geddy", "test": "npm run jshint && npm run cover" }, "ava": { "files": [ - "test/appengine/**/*.test.js" + "test/**/*.test.js" ] }, "devDependencies": { diff --git a/test/vision/faceDetection.test.js b/test/vision/faceDetection.test.js new file mode 100644 index 0000000000..e09c7df5f5 --- /dev/null +++ b/test/vision/faceDetection.test.js @@ -0,0 +1,65 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var test = require('ava'); +var fs = require('fs'); +var path = require('path'); + +function MockCanvas () { + this.getContext = function () { + return { + drawImage: function () {}, + beginPath: function () {}, + lineTo: function () {}, + stroke: function () {} + }; + }; + this.pngStream = function () { + return { + on: function (event, cb) { + if (event === 'end') { + setTimeout(function () { + cb(); + }, 1000); + } else if (event === 'data') { + cb('test'); + cb('foo'); + cb('bar'); + } + } + }; + }; +} + +MockCanvas.Image = function () {}; + +var faceDetectionExample = require('../../vision/faceDetection'); +var inputFile = path.resolve(path.join('../../vision', 'face.png')); +var outputFile = path.resolve(path.join('../../vision', 'out.png')); + +test.cb('should detect faces', function (t) { + faceDetectionExample.main( + inputFile, + outputFile, + MockCanvas, + function (err, faces) { + t.ifError(err); + t.is(faces.length, 1); + var image = fs.readFileSync(outputFile); + t.is(image.toString('utf8'), 'testfoobar'); + t.end(); + } + ); +}); diff --git a/vision/.gitignore b/vision/.gitignore new file mode 100644 index 0000000000..3e5a87a1bd --- /dev/null +++ b/vision/.gitignore @@ -0,0 +1 @@ +out.* \ No newline at end of file diff --git a/vision/README.md b/vision/README.md new file mode 100644 index 0000000000..860b81b347 --- /dev/null +++ b/vision/README.md @@ -0,0 +1,30 @@ +## Cloud Vision API samples + +These samples require two environment variables to be set: + +- `GOOGLE_APPLICATION_CREDENTIALS` - Path to a service account file. You can +download one from your Google project's "credentials" page. +- `GCLOUD_PROJECT` - ID of your Google project. + +See [gcloud-node authentication][auth] for more details. + +[auth]: https://googlecloudplatform.github.io/gcloud-node/#/docs/guides/authentication + +## Run a sample + +Install dependencies first: + + npm install + +### Face detection sample + +This sample uses [node-canvas](https://github.com/Automattic/node-canvas) to +draw an output image. node-canvas depends on Cairo, which may require separate +installation. See the node-canvas [installation section][canvas-install] for +details. + +[canvas-install]: https://github.com/Automattic/node-canvas#installation + +Execute the sample: + + node faceDetection "/path/to/image.jpg" diff --git a/vision/face.png b/vision/face.png new file mode 100644 index 0000000000..b613a94e34 Binary files /dev/null and b/vision/face.png differ diff --git a/vision/faceDetection.js b/vision/faceDetection.js new file mode 100644 index 0000000000..66b2c0a465 --- /dev/null +++ b/vision/faceDetection.js @@ -0,0 +1,120 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START auth] +// You must set the GOOGLE_APPLICATION_CREDENTIALS and GCLOUD_PROJECT +// environment variables to run this sample. See: +// https://github.com/GoogleCloudPlatform/gcloud-node/blob/master/docs/authentication.md +var projectId = process.env.GCLOUD_PROJECT; + +// Initialize gcloud +var gcloud = require('gcloud')({ + projectId: projectId +}); + +// Get a reference to the vision component +var vision = gcloud.vision(); +// [END auth] + +var fs = require('fs'); + +/** + * Uses the Vision API to detect faces in the given file. + */ +function detectFaces(inputFile, callback) { + // Make a call to the Vision API to detect the faces + vision.detectFaces(inputFile, function (err, faces) { + if (err) { + return callback(err); + } + var numFaces = faces.length; + console.log('Found ' + numFaces + (numFaces === 1 ? ' face' : ' faces')); + callback(null, faces); + }); +} + +/** + * Draws a polygon around the faces, then saves to outputFile. + */ +function highlightFaces(inputFile, faces, outputFile, Canvas, callback) { + fs.readFile(inputFile, function (err, image) { + if (err) { + return callback(err); + } + + var Image = Canvas.Image; + // Open the original image into a canvas + var img = new Image(); + img.src = image; + var canvas = new Canvas(img.width, img.height); + var context = canvas.getContext('2d'); + context.drawImage(img, 0, 0, img.width, img.height); + + // Now draw boxes around all the faces + context.strokeStyle = 'rgba(0,255,0,0.8)'; + context.lineWidth = '5'; + + faces.forEach(function (face) { + context.beginPath(); + face.bounds.face.forEach(function (bounds) { + context.lineTo(bounds.x, bounds.y); + }); + context.lineTo(face.bounds.face[0].x, face.bounds.face[0].y); + context.stroke(); + }); + + // Write the result to a file + console.log('Writing to file ' + outputFile); + var writeStream = fs.createWriteStream(outputFile); + var pngStream = canvas.pngStream(); + + pngStream.on('data', function (chunk) { + writeStream.write(chunk); + }); + pngStream.on('error', console.log); + pngStream.on('end', callback); + }); +} + +// Run the example +function main(inputFile, outputFile, Canvas, callback) { + outputFile = outputFile || 'out.png'; + detectFaces(inputFile, function (err, faces) { + if (err) { + return callback(err); + } + + console.log('Highlighting...'); + highlightFaces(inputFile, faces, outputFile, Canvas, function (err) { + if (err) { + return callback(err); + } + console.log('Finished!'); + callback(null, faces); + }); + }); +} + +exports.main = main; + +if (module === require.main) { + if (process.argv.length < 3) { + console.log('Usage: node faceDetection [outputFile]'); + process.exit(1); + } + var inputFile = process.argv[2]; + var outputFile = process.argv[3]; + exports.main(inputFile, outputFile, require('canvas'), console.log); +} diff --git a/vision/package.json b/vision/package.json new file mode 100644 index 0000000000..989ac3c8e9 --- /dev/null +++ b/vision/package.json @@ -0,0 +1,17 @@ +{ + "name": "cloud-vision-samples", + "description": "Node.js samples for Google Cloud Vision.", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "engines": { + "node": ">=0.10.x" + }, + "scripts": { + "faceDetection": "node faceDetection.js" + }, + "dependencies": { + "gcloud": "^0.32.0", + "canvas": "^1.3.15" + } +}