PHP项目图像分类与ResNet

wen PHP项目 2

PHP项目中的图像分类与ResNet实现

在PHP项目中实现基于ResNet的图像分类,通常有几种可行的方案,让我详细介绍各种方法:

PHP项目图像分类与ResNet

使用PHP扩展 + 预训练模型

安装PHP-ML扩展与ONNX Runtime

// 使用ONNX Runtime PHP扩展
composer require onnxruntime/onnxruntime
// 加载ResNet模型进行推理
use ONNX\Runtime;
$runtime = new Runtime();
$model = $runtime->loadModel('resnet50.onnx');
// 图像预处理
function preprocessImage($imagePath) {
    $image = imagecreatefromjpeg($imagePath);
    $resized = imagescale($image, 224, 224);
    // 转换为RGB数组
    $pixels = [];
    for ($y = 0; $y < 224; $y++) {
        for ($x = 0; $x < 224; $x++) {
            $rgb = imagecolorat($resized, $x, $y);
            $r = ($rgb >> 16) & 0xFF;
            $g = ($rgb >> 8) & 0xFF;
            $b = $rgb & 0xFF;
            // ImageNet标准化
            $pixels[] = ($r / 255 - 0.485) / 0.229;
            $pixels[] = ($g / 255 - 0.456) / 0.224;
            $pixels[] = ($b / 255 - 0.406) / 0.225;
        }
    }
    return $pixels;
}
// 执行分类
$input = preprocessImage('test.jpg');
$result = $model->predict([$input]);
$classId = array_search(max($result), $result);
echo "预测类别ID: " . $classId;

使用Python微服务架构(推荐)

这是最实用的方案,将模型推理部署为独立服务:

Python端 (Flask服务)

# app.py
from flask import Flask, request, jsonify
from PIL import Image
import torch
import torchvision.transforms as transforms
import torchvision.models as models
import numpy as np
import io
app = Flask(__name__)
# 加载预训练ResNet模型
model = models.resnet50(pretrained=True)
model.eval()
# 图像预处理
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                        std=[0.229, 0.224, 0.225])
])
# 加载ImageNet类别标签
with open('imagenet_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()]
@app.route('/classify', methods=['POST'])
def classify_image():
    if 'image' not in request.files:
        return jsonify({'error': 'No image uploaded'}), 400
    file = request.files['image']
    image = Image.open(io.BytesIO(file.read())).convert('RGB')
    # 预处理
    input_tensor = transform(image).unsqueeze(0)
    # 推理
    with torch.no_grad():
        outputs = model(input_tensor)
        _, predicted = outputs.max(1)
        probabilities = torch.nn.functional.softmax(outputs[0], dim=0)
    # 获取Top-5结果
    top5_prob, top5_indices = torch.topk(probabilities, 5)
    results = []
    for i in range(5):
        idx = top5_indices[i].item()
        results.append({
            'class': labels[idx],
            'class_id': idx,
            'probability': top5_prob[i].item()
        })
    return jsonify({'predictions': results})
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

PHP端调用

<?php
// PHP调用Python微服务
class ImageClassifier {
    private $serviceUrl;
    public function __construct($serviceUrl = 'http://localhost:5000') {
        $this->serviceUrl = $serviceUrl;
    }
    public function classify($imagePath) {
        $imageData = file_get_contents($imagePath);
        $ch = curl_init($this->serviceUrl . '/classify');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            'image' => new CURLFile($imagePath)
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);
        return json_decode($response, true);
    }
}
// 使用示例
$classifier = new ImageClassifier();
$result = $classifier->classify('cat.jpg');
echo "图像分类结果:\n";
foreach ($result['predictions'] as $prediction) {
    echo sprintf(
        "%s (%.2f%%)\n",
        $prediction['class'],
        $prediction['probability'] * 100
    );
}

使用TensorFlow Serving + PHP

导出ResNet模型

# 导出为SavedModel格式
import tensorflow as tf
import tensorflow_hub as hub
model = tf.keras.Sequential([
    hub.KerasLayer("https://tfhub.dev/google/imagenet/resnet_v2_50/classification/5"),
])
# 保存模型
tf.saved_model.save(model, "resnet_saved_model")

Docker部署TensorFlow Serving

# Dockerfile
FROM tensorflow/serving
COPY resnet_saved_model /models/resnet/1
ENV MODEL_NAME=resnet

PHP客户端

<?php
// 使用gRPC或HTTP调用TensorFlow Serving
// HTTP REST方式
function classifyWithTFServing($imagePath) {
    $image = base64_encode(file_get_contents($imagePath));
    $requestData = [
        'instances' => [
            ['b64' => $image]
        ]
    ];
    $ch = curl_init('http://localhost:8501/v1/models/resnet:predict');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestData));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

完整的PHP项目示例

<?php
// ImageClassifier.php - 完整的图像分类类
class ImageClassifier {
    private $modelPath;
    private $labels;
    private $pythonPath;
    private $tempDir;
    public function __construct($config = []) {
        $this->modelPath = $config['model_path'] ?? '/models/resnet50.onnx';
        $this->labels = $this->loadLabels($config['labels_path'] ?? 'imagenet_labels.txt');
        $this->pythonPath = $config['python_path'] ?? 'python3';
        $this->tempDir = $config['temp_dir'] ?? sys_get_temp_dir();
    }
    public function classify($imagePath, $topK = 5) {
        // 验证图像
        if (!file_exists($imagePath)) {
            throw new Exception("图像文件不存在: $imagePath");
        }
        // 创建临时Python脚本
        $script = $this->createPythonScript($imagePath, $topK);
        $scriptPath = $this->tempDir . '/classify_' . uniqid() . '.py';
        file_put_contents($scriptPath, $script);
        // 执行Python推理
        $command = sprintf(
            '%s %s 2>&1',
            escapeshellcmd($this->pythonPath),
            escapeshellarg($scriptPath)
        );
        $output = shell_exec($command);
        unlink($scriptPath);
        // 解析结果
        $result = json_decode($output, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("解析推理结果失败: " . json_last_error_msg());
        }
        return $result;
    }
    private function createPythonScript($imagePath, $topK) {
        return <<<PYTHON
import json
import sys
from PIL import Image
import torch
import torchvision.transforms as transforms
import torchvision.models as models
# 加载模型
model = models.resnet50(pretrained=True)
model.eval()
# 图像预处理
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                        std=[0.229, 0.224, 0.225])
])
# 加载图像
image = Image.open('$imagePath').convert('RGB')
input_tensor = transform(image).unsqueeze(0)
# 推理
with torch.no_grad():
    outputs = model(input_tensor)
    probabilities = torch.nn.functional.softmax(outputs[0], dim=0)
    top_probs, top_indices = torch.topk(probabilities, $topK)
# 加载标签
with open('{$this->labels}', 'r') as f:
    labels = [line.strip() for line in f.readlines()]
# 构建结果
results = []
for i in range($topK):
    idx = top_indices[i].item()
    results.append({
        'class': labels[idx],
        'class_id': idx,
        'probability': top_probs[i].item()
    })
print(json.dumps(results))
PYTHON;
    }
    private function loadLabels($path) {
        if (!file_exists($path)) {
            // 返回默认标签或下载
            return $this->downloadLabels();
        }
        return $path;
    }
    private function downloadLabels() {
        $url = "https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json";
        $path = $this->tempDir . '/imagenet_labels.json';
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        $data = curl_exec($ch);
        curl_close($ch);
        file_put_contents($path, $data);
        return $path;
    }
}
// 使用示例
try {
    $classifier = new ImageClassifier();
    $results = $classifier->classify('uploads/cat.jpg', 3);
    echo "<h2>图像分类结果</h2>";
    echo "<table border='1'>";
    echo "<tr><th>类别</th><th>置信度</th></tr>";
    foreach ($results as $result) {
        echo sprintf(
            "<tr><td>%s</td><td>%.2f%%</td></tr>",
            htmlspecialchars($result['class']),
            $result['probability'] * 100
        );
    }
    echo "</table>";
} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}

性能优化建议

  1. 缓存模型加载:避免每次请求都重新加载模型
  2. 批量处理:合并多个图像请求进行批量推理
  3. 使用GPU:如果有GPU资源,确保模型在GPU上运行
  4. 图像预处理缓存:对相同尺寸的图像缓存预处理结果
  5. 异步处理:使用消息队列处理图像分类请求

部署建议

# docker-compose.yml
version: '3.8'
services:
  php-app:
    build: .
    ports:
      - "8080:80"
    volumes:
      - ./app:/var/www/html
    depends_on:
      - classifier-service
  classifier-service:
    build: ./classifier
    ports:
      - "5000:5000"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

这种微服务架构是最推荐的方案,它既保留了PHP的优势(Web处理、简单部署),又能充分利用Python的深度学习生态。

抱歉,评论功能暂时关闭!