Face detection with IBM Watson

316 Views Asked by At

Im trying to make a simple image uploader with React and integrate it with IBM Watson to do a face detection, just draw a square in the faces of a image.

So, this is my Index.js.

    import React from 'react';
import PropTypes from 'prop-types';
import './index.css';
import FlipMove from 'react-flip-move';
import UploadIcon from './UploadIcon.svg';

const VisualRecognitionV3 = require('watson-deve`enter code here`loper-cloud/visual-recognition/v3');
const visualRecognition = new VisualRecognitionV3({
  version: '2018-03-19',
  iam_apikey: '{j3YBm86Ep4cNupisk1a7xhcokOMpPO5LYHwdTJcjfw9k}'
});

const styles = {
 display: "flex",
 alignItems: "center",
 justifyContent: "center",
 flexWrap: "wrap",
 width: "100%"
};

class ReactImageUploadComponent extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   pictures: [],
            files: [],
            notAcceptedFileType: [],
   notAcceptedFileSize: []
  };
  this.inputElement = '';
  this.onDropFile = this.onDropFile.bi`enter code here`nd(this);
  this.triggerFileUpload = this.triggerFileUpload.bind(this);
 }

 /*
  On button click, trigger input file to open
  */
 triggerFileUpload() {
  this.inputElement.click();
 }

 /*
  Handle file validation
  */
 onDropFile(e, pictureFiles, pictureDataURLs) {
  const files = e.target.files;
  const _this = this;
  visualRecognition.detectFaces({image_file: pictureFiles[0]}, function(err, response){
            if (err){
                console.log(err);
            } else {
                console.log(JSON.stringify(response, null, 2));
            }
        });

  // Iterate over all uploaded files
  for (let i = 0; i < files.length; i++) {
      let f = files[i];
   // Check for file extension
   if (!this.hasExtension(f.name)) {
    const newArray = _this.state.notAcceptedFileType.slice();
    newArray.push(f.name);
    _this.setState({notAcceptedFileType: newArray});
    continue;
   }
   // Check for file size
   if(f.size > this.props.maxFileSize) {
    const newArray = _this.state.notAcceptedFileSize.slice();
    newArray.push(f.name);
    _this.setState({notAcceptedFileSize: newArray});
    continue;
   }

   const reader = new FileReader();
   // Read the image via FileReader API and save image result in state.
   reader.onload = (function () {
    return function (e) {
                    // Add the file name to the data URL
                    let dataURL = e.target.result;
                    dataURL = dataURL.replace(";base64", `;name=${f.name};base64`);

                    if (_this.props.singleImage === true) {
                        _this.setState({pictures: [dataURL], files: [f]}, () => {
                            _this.props.onChange(_this.state.files, _this.state.pictures);
                        });
                    } else if (_this.state.pictures.indexOf(dataURL) === -1) {
                        const newArray = _this.state.pictures.slice();
                        newArray.push(dataURL);

                        const newFiles = _this.state.files.slice();
                        newFiles.push(f);

                        _this.setState({pictures: newArray, files: newFiles}, () => {
                            _this.props.onChange(_this.state.files, _this.state.pictures);
                        });
                    }
    };
   })(f);
   reader.readAsDataURL(f);
  }
 }

  /*
   Render the upload icon
   */
  renderIcon() {
  if (this.props.withIcon) {
      return <img src={UploadIcon} className="uploadIcon" alt="Upload Icon" />;
  }
 }

 /*
  Render label
  */
 renderLabel() {
  if (this.props.withLabel) {
    return <p className={this.props.labelClass} style={this.props.labelStyles}>{this.props.label}</p>
  }
 }

  /*
  Check file extension (onDropFile)
  */
 hasExtension(fileName) {
        const pattern = '(' + this.props.imgExtension.join('|').replace(/\./g, '\\.') + ')$';
        return new RegExp(pattern, 'i').test(fileName);
 }

 /*
  Remove the image from state
  */
 removeImage(picture) {
        const removeIndex = this.state.pictures.findIndex(e => e === picture);
        const filteredPictures = this.state.pictures.filter((e, index) => index !== removeIndex);
        const filteredFiles = this.state.files.filter((e, index) => index !== removeIndex);

        this.setState({pictures: filteredPictures, files: filteredFiles}, () => {
            this.props.onChange(this.state.files, this.state.pictures);
        });
 }

 /*
  Check if any errors && render
  */
 renderErrors() {
  let notAccepted = '';
  if (this.state.notAcceptedFileType.length > 0) {
   notAccepted = this.state.notAcceptedFileType.map((error, index) => {
    return (
     <div className={'errorMessage ' + this.props.errorClass} key={index} style={this.props.errorStyle}>
      * {error} {this.props.fileTypeError}
     </div>
    )
   });
  }
  if (this.state.notAcceptedFileSize.length > 0) {
   notAccepted = this.state.notAcceptedFileSize.map((error, index) => {
    return (
     <div className={'errorMessage ' + this.props.errorClass} key={index} style={this.props.errorStyle}>
      * {error} {this.props.fileSizeError}
     </div>
    )
   });
  }
  return notAccepted;
 }

 /*
  Render preview images
  */
 renderPreview() {
  return (
   <div className="uploadPicturesWrapper">
    <FlipMove enterAnimation="fade" leaveAnimation="fade" style={styles}>
     {this.renderPreviewPictures()}
    </FlipMove>
   </div>
  );
 }

 renderPreviewPictures() {
  return this.state.pictures.map((picture, index) => {
   return (
    <div key={index} className="uploadPictureContainer">
     <div className="deleteImage" onClick={() => this.removeImage(picture)}>X</div>
     <img src={picture} className="uploadPicture" alt="preview"/>
    </div>
   );
  });
 }

 render() {
  return (
   <div className={"fileUploader " + this.props.className} style={this.props.style}>
    <div className="fileContainer">
     {this.renderIcon()}
     {this.renderLabel()}
     <div className="errorsContainer">
      {this.renderErrors()}
     </div>
     <button
                        type={this.props.buttonType}
      className={"chooseFileButton " + this.props.buttonClassName}
      style={this.props.buttonStyles}
      onClick={this.triggerFileUpload}
     >
                        {this.props.buttonText}
     </button>
     <input
      type="file"
      ref={input => this.inputElement = input}
      name={this.props.name}
      multiple="multiple"
      onChange={this.onDropFile}
      accept={this.props.accept}
     />
     { this.props.withPreview ? this.renderPreview() : null }
    </div>
   </div>
  )
 }
}

ReactImageUploadComponent.defaultProps = {
 className: '',
 buttonClassName: "",
 buttonStyles: {},
 withPreview: false,
 accept: "image/*",
 name: "",
 withIcon: true,
 buttonText: "Escolher Imagem",
    buttonType: "submit",
 withLabel: true,
 label: "Tamanho máximo de arquivo: 5mb, formatos aceitos: jpg,gif,png",
 labelStyles: {},
 labelClass: "",
 imgExtension: ['.jpg', '.gif', '.png'],
 maxFileSize: 5242880,
 fileSizeError: " arquivo muito grande",
 fileTypeError: " extenção de arquivo não suportada",
 errorClass: "",
 style: {},
 errorStyle: {},
 singleImage: false,
    onChange: () => {}
};

ReactImageUploadComponent.propTypes = {
 style: PropTypes.object,
 className: PropTypes.string,
 onChange: PropTypes.func,
  onDelete: PropTypes.func,
 buttonClassName: PropTypes.string,
 buttonStyles: PropTypes.object,
    buttonType: PropTypes.string,
 withPreview: PropTypes.bool,
 accept: PropTypes.string,
 name: PropTypes.string,
 withIcon: PropTypes.bool,
 buttonText: PropTypes.string,
 withLabel: PropTypes.bool,
 label: PropTypes.string,
 labelStyles: PropTypes.object,
 labelClass: PropTypes.string,
 imgExtension: PropTypes.array,
 maxFileSize: PropTypes.number,
 fileSizeError: PropTypes.string,
 fileTypeError: PropTypes.string,
 errorClass: PropTypes.string,
 errorStyle: PropTypes.object,
  singleImage: PropTypes.bool
};

export default ReactImageUploadComponent;

I have already tried some tutorials and examples from web but no success. The question is, how do i apply Watson to draw a square around the face of the uploaded image?

1

There are 1 best solutions below

1
Sam On

1) I don't know what you were attempting to do with const steps, but it doesn't make any sense. You don't need an npm install command inside of your React app. I also don't see any purpose of including a bunch of JavaScript inside of a string.

2) Inside of your actual App class at the bottom, where you're using the ImageUploader component, you have not followed your own advice from the steps above and included an onChange method. From what I can see from the ImageUploader component documentation, that is what you'll use to perform a certain an action after the user uploads an image.

3) You have not made any attempt to actually integrate the IBM Watson API into this code. Obviously that is going to be a critical step in achieving your stated goal. You should make a sincere attempt yourself before posting a question here.

Anyway, the basic idea of what you need to do is, after the user uploads an image, you make an API request to Watson's Image Recognition API: https://www.ibm.com/watson/developercloud/visual-recognition/api/v3/node.html?node#detect-faces

You'll need to set up an account with them and get a developer key (it should be free for a very small number of requests). Then, you'll want to install their API using npm install --save watson-developer-cloud (note: as I mentioned above, this does not belong in your code, like you have now; this is meant to be run from your Terminal/shell in the project directory.

At the top of your file you'll include the require statement for the SDK:
const VisualRecognitionV3 = require('watson-developer-cloud/visual-recognition/v3');

And create an instance of the SDK using your API key:

const visualRecognition = new VisualRecognitionV3({
  version: '2018-03-19',
  iam_apikey: '{iam_api_key}'
});

Finally, once the user uploads an image, you call the appropriate function using the image file as a parameter:

visualRecognition.detectFaces({image_file: file}, function(err, response) {
  if (err)
    console.log(err);
  else
    console.log(JSON.stringify(response, null, 2))
});

Put it all together and your app might look something like this:

import React from 'react';
import ImageUploader from 'react-images-upload';
const VisualRecognitionV3 = require('watson-developer-cloud/visual-recognition/v3'); 

const visualRecognition = new VisualRecognitionV3({
  version: '2018-03-19',
  iam_apikey: '{iam_api_key}'
});

class App extends React.Component {

    constructor(props) {
        super(props);
        this.onDrop = this.onDrop.bind(this);
    }

    onDrop(pictureFiles, pictureDataURLs) {
        visualRecognition.detectFaces({image_file: pictureFiles[0]}, function(err, response) {
            if (err) {
                console.log(err);
            } else
                console.log(JSON.stringify(response, null, 2));
            }
       });
    }

    render() {
        return (
            <ImageUploader
                withIcon={true}
                buttonText='Escolher Imagens'
                onChange={this.onDrop}
                imgExtension={['.jpg', '.gif', '.png', '.gif']}
                maxFileSize={5242880}
            />
        );
    }
}

I haven't tested this code so the IBM Watson SDK may have an issue with the format of pictureFile[0] (and also I'm not sure if pictureFiles is even an array, but you should easily check the React-Images-Uploader component to see what data structure that is). But, regardless, this is the basic idea.

By the way, the output of this code will be in the JavaScript developer console, and it will print out the dimensions and coordinates of the square around the face(s) in the image, but this code won't draw it for you. That's another task and there's a bunch of ways to do and you can ask that elsewhere. But here you'll get the coordinates, shape and size of the faces and then they just need to be drawn.