PHP and Python integration from scratch on Laravel

Introduction

Let's implement a software based on Laravel that executes Python from PHP and displays the result of image recognition.

The implementation of image recognition is Try learning MNIST with TensorFlow, which is based on the separate learning and testing. To.

[2020/4/15] Introduced Bootstrap in view

environment

--OS: Ubuntu 18.04.4 LTS (on Virtual BOX)

Python3, which is included in Ubuntu 18.04.4 LTS by default, was 3.6.9, so I have updated it. At the same time, include the library used in Python.

$sudo apt install python3.8
$sudo apt-get install python3-pip python3-dev
$pip3 install tensorflow
$pip3 install numpy
$pip3 install opencv-python

Note that if you install with the $ sudo apt-get install python-pip python-dev and pip commands, the Python2 library will be included.

Rewrite "~ / .bashrc" to use python3.8.

..bashrc


alias python3='/usr/bin/python3.8'

Creating a project

Create a project named laravelAI.

$ composer create-project laravel/laravel laravelAI --prefer-dist

Directory / file structure

Only the items to be added / corrected this time are listed. The deficit is added. laravelAI  ┗ app   ┗ Http    ┗ MnistController.php Python </ font>: Stores Python related files    ┗ mnist_test.py    ┗ DeepConvNet.py ckpt </ font>: Store learning results  ┗ resources   ┗ views    ┗ mnist     ┗ index.blade.php  ┗ routes   ┗ wab.php

PHP (controller)

Create a controller.

$ php artisan make:controller MnistController

MnistController.php


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MnistController extends Controller
{
    public function index(Request $request)
    {
        $data = [
            'msg' => "Please enter an image",
        ];
        return view('mnist.index', $data);
    }

    public function test(Request $request)
    {
        if(!empty($request->test_image))
        {
            //Get the image sent by POST
            $image = $request->file('test_image');
            //The save destination is"storage/app/public/image"Will be
            //File name will be assigned automatically
            $up_pass = $image->store('public/image');
            $image_pass = "./storage/image/".basename($up_pass);
            //Set the path where the Python file is
            $pythonPath =  "../app/Python/";
            $command = "/usr/bin/python3 " . $pythonPath . "mnist_test.py " . $pythonPath . " " .$image_pass;
            exec($command , $outputs);

            //Extracting result rows with regular expressions
            $results = preg_grep('/result:.*/' , $outputs);
            if(count($results) == 1){
                //Get the beginning of an associative array
                $result = reset($results);
                $result = substr($result , strlen('result:') , 1 );
            } 
            else {
                $result = "Analysis failed.";
            }
            $data = [
                'msg' => "Do you have a?",
                'image_pass' => $image_pass,
                'result'  => $result,
                'lines'   => count($outputs),
                'outputs' => $outputs,
            ];
        }
        else {
            $data = [
                'msg' => "there is no image",
            ];
        }
        return view('mnist.index', $data);
    }
}

Description

Execution of Python commands

//Set the path where the Python file is
$pythonPath =  "../app/Python/";
$command = "/usr/bin/python3 " . $pythonPath . "mnist_test.py " . $pythonPath . " " .$image_pass;
exec($command , $outputs);

Executing commands from PHP uses the ʻexec ()` function. Specify the command to be executed in the first argument, and specify the variable that stores the output result of the command in the second argument. See here for details.

Run Python in the / usr / bin / python3 Python file. The $ command string above looks like this: /usr/bin/python3 ../app/Python/mnist_test.py ../app/Python/ ./storage/image/FxY92Ji6j04cyozrx62yGHu9NQzLgsIqQq23YIcH.png

If you modify the command as $ command =" pwd "; and execute it, you can see that the execution path of Laravel is "/ home / hogehoge / laravelAI / public". Assuming that, specify the location where the Python file is located.

"Learning data path" is specified in the first argument of mnist_test.py, and" image path "is specified in the second argument.

Regarding the storage location of the received image

//Get the image sent by POST
$image = $request->file('test_image');
$up_pass = $image->store('public/image');
$image_pass = "./storage/image/".basename($up_pass);

The image will be saved in "storage / app / public / image". Make a symbolic link from "public / storage" to "storage / app / public" with the following command. For the second argument of mnist_test.py and the image displayed in the view, specify the path under" public / storage ".

$ php artisan storage:link
$ cd public
$ ls -la
=> storage -> /home/hogehoge/laravelAI/storage/app/public

PHP (routing)

Add the following to routes / web.php.

routes/web.php


Route::get ('mnist', 'MnistController@index');
Route::post('mnist', 'MnistController@test');

Pyhton As for the learning result, the one of Try learning MNIST with TensorFlow can be used as it is. Store it in the "Python / ckpt" folder.

The test code will be modified slightly. Allows you to specify "training data path" and "image path" with command line arguments.

mnist_test.py


# -*- coding: utf-8 -*-
print('mnist_test.py START')
import sys
import tensorflow as tf
import cv2
import numpy as np
import DeepConvNet as CNN
#Get command line arguments
args = sys.argv

IMAGE_SIZE  = 28    #Image size
NUM_CLASSES = 10    #Number of identifications

if __name__ == "__main__":
    tf.reset_default_graph()
    
    print('Setting START')
    #Variable settings used in expressions
    x_image = tf.placeholder("float", shape=[None, IMAGE_SIZE * IMAGE_SIZE])    #input
    y_label = tf.placeholder("float", shape=[None, NUM_CLASSES]) #output
    keep_prob = tf.placeholder("float")

    #Create a model
    logits = CNN.CNN.makeMnistCNN(x_image, keep_prob , IMAGE_SIZE , NUM_CLASSES)
    sess = tf.InteractiveSession()

    saver = tf.train.Saver()
    #Initialize and execute variables
    sess.run(tf.global_variables_initializer())
    print('Setting END')
    
    print('Restore Param Start')
    ckpt = tf.train.get_checkpoint_state(args[1] + 'ckpt')
    #print(os.path.exists(args[1] + 'ckpt'))
    if ckpt: #If there is a checkpoint
        last_model = ckpt.model_checkpoint_path #Path to the last saved model
        print ("Restore load:" + last_model)
        saver.restore(sess, last_model) #Reading variable data
    else:
        print('Restore Failed')
    print('Restore Param End')
    
    
    #Image loading
    inputNum = 1
    for count in range(int(inputNum)):
        fileName =  args[2]
        print('fileName:' + fileName)

        #Initialization
        ximage = []
        
        #Image loading
        image = cv2.imread(fileName, cv2.IMREAD_GRAYSCALE)
        if not image is None:
            image = cv2.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
            ximage = image.flatten().astype(np.float32)/255.0
        else:
            print('Error:File Read Failed !!')

        if len(ximage)!=0:
            pred = np.argmax(logits.eval(feed_dict={x_image: [ximage], keep_prob: 1.0})[0])
            
            print("result:" + str(pred))
    sess.close()
    print('mnist_test.py END')

PHP (view)

Create a view. The layout is suitable.

index.blade.php  //.html


<html>
<head>
    <title>Mnist</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" >
    <style>
        body{
            background-color: #EEFFEE;
        }
    </style>
</head>

<body>
<div class="container">
        <div class="row">
            <div class="col-sm-10">
                <h2>1-Recognize the number 9</h2>
                <p>{{$msg}}</p>

                <form method = "POST" action="/mnist" enctype="multipart/form-data">
                    @csrf
                    <input type="file" name="test_image"><BR>
                    <input type="submit" id="submit" value="Send">
                </form>
            </div>
        </div>

        <div class="row">
            @isset($image_pass)
                <div class="col-sm-5">
                    <h3>result</h3>
                    <img src="{{ asset($image_pass)}}" width="112" height="112"> <BR>
                    @isset($result)
                        <p>This image is "{{$result}}"is</p>
                    @endempty
                </div>
            @endempty

            @isset($outputs)
                <div class="col-sm-5">
                    <h3>Python output( {{$lines}}line)</h3>
                    @foreach ($outputs as $output)
                        {{$output}}<BR>
                    @endforeach
                </div>
            @endempty
        </div>
    </div>
</body>
</html>

Operation check

Start the server with the following command.

$ php artisan serve 

Go to "http: // localhost: 8000 / mnist" in your browser. WS000226.JPG

Select a file and send it. WS000225.JPG

I was able to recognize it!

At the end

The introduction of Vue and TypeScript is described in PHP and Python integration from scratch on Laravel + Vue + TypeScript.

Supplement

Exception on saving file: The "" file does not exist or is not readable.

When I was messing with the code in another case, I ran into the above exception with $ image-> store () in the file save, so I caught it.

Symfony\Component\Mime\Exception\InvalidArgumentException
The "" file does not exist or is not readable. 

It's hard to tell from the exception message or call stack, but it's because you're stuck with an uploaded file size limit.

Countermeasures

The file size is defined in the php.ini file, so modify it. (I searched online and found a way to fix .htaccess, but fixing public / .htaccess didn't help.)

# php.Check the location of ini
$ php -i | grep php.ini
Configuration File (php.ini) Path => /etc/php/7.2/cli
Loaded Configuration File => /etc/php/7.2/cli/php.ini

$ sudo vi  /etc/php/7.2/cli/php.ini

Modify the following two lines as needed. In PHP7.2, it defaults to 2MB and 8MB.

php.ini


;Maximum total size of all files uploaded by POST
post_max_size=2M
;Maximum size per file
upload_max_filesize=8M

Please restart the server after making the change.

$ php artisan  serve

Recommended Posts