liuhairui 4 сар өмнө
parent
commit
913a07d0b2

+ 30 - 35
application/api/controller/WorkOrder.php

@@ -28,11 +28,12 @@ class WorkOrder extends Api
 
 
 
-
+    //单独方法本地测试
     public function imgtowimg()
     {
-        $prompt = $this->request->param('prompt', '将图片纵向扩展至1248像素');
-        $imgRelPath = 'uploads/operate/ai/Preview/arr/一朵盛开的白色牡丹花为主体采用厚涂技法花心和背景点缀金箔灰银.png';
+        $prompt = $this->request->param('prompt', '');
+        $imgRelPath = 'uploads/operate/ai/Preview/arr/两只手碰拳的黑白线条插画下有Daddy  Me字样风格简约质.png';
+
         $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
 
         if (!file_exists($imgPath)) {
@@ -40,52 +41,44 @@ class WorkOrder extends Api
         }
 
         $imgData = file_get_contents($imgPath);
-        $base64Img = base64_encode($imgData);
-        $initImage = 'data:image/png;base64,' . $base64Img;
+        $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
 
-        $postData = json_encode([
-            'prompt' => "1girl",
-            'negative_prompt' => '(nsfw),sketches,tattoo,(beard:1.3,(EasyNegative:1.3),badhandv,(Teeth:1.3),(worst quality:2),(low quality:2),(normal quality:2),lowers,normal quality,facing away,looking away,text,error,extra digit,fewer digits,cropped,jpeg artifacts,signature,watermark,username,blurry,skin spots,acnes,skin blemishes,bad anatomy,fat,bad feet,poorly drawn hands,poorly drawn face,mutation,deformed,tilted hands,extra fingers,extra limbs,extra arms,extra legs,malformed proportions,gross proportions,missing fingers,missing toes)',
+        $params = [
+            'prompt' => $prompt,
+            'negative_prompt' => '(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy',
             'steps' => 20,
+            'sampler_name' => 'DPM++ 2M SDE',
             'cfg_scale' => 7,
             'seed' => -1,
-            'clip_skip' => 7,
-            'denoising_strength' => 0.2,
             'width' => 1024,
-            'height' => 1248,
-            'resize_mode' => 2, // 缩放后填充空白
-            'inpainting_fill' => 0, // 保留原图内容
-            'mask_blur' => 0,
-            'sampler_name' => 'Euler a',
-            'init_images' => [$initImage],
+            'height' => 1303,
+
             'override_settings' => [
-                'sd_model_checkpoint' => 'Realistic_Vision_V5.0-inpainting',
+                'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting',
                 'sd_vae' => 'vae-ft-mse-840000-ema-pruned',
-                'CLIP_stop_at_last_layers' => 7
+                'CLIP_stop_at_last_layers' => 2
             ],
-            'override_settings_restore_afterwards' => true,
+            'clip_skip' => 2,
+
             'alwayson_scripts' => [
-                'ControlNet' => [
+                'controlnet' => [
                     'args' => [[
-                        'input_image' => null,
+                        'enabled' => true,
+                        'input_image' => $base64Img,
                         'module' => 'inpaint_only+lama',
-                        'model' => 'control_v11p_sd15_openpose [cab727d4]',
-                        'weight' => 1.0,
+                        'model' => 'control_v11p_sd15_inpaint_fp16 [be8bc0ed]',
+                        'weight' => 1,
                         'resize_mode' => 'Resize and Fill',
-                        'lowvram' => false,
-                        'processor_res' => 512,
-                        'threshold_a' => 64,
-                        'threshold_b' => 64,
-                        'guidance_start' => 0.0,
-                        'guidance_end' => 1.0,
-                        'pixel_perfect' => true,
-                        'control_mode' => 2 // 更偏向ControlNet
+                        'pixel_perfect' => false,
+                        'control_mode' => 'ControlNet is more important',
+                        'starting_control_step' => 0,
+                        'ending_control_step' => 1
                     ]]
                 ]
             ]
-        ]);
+        ];
 
-        $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img";
+        $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img";
         $headers = ['Content-Type: application/json'];
 
         $ch = curl_init();
@@ -93,8 +86,9 @@ class WorkOrder extends Api
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($ch, CURLOPT_POST, true);
         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
-        curl_setopt($ch, CURLOPT_TIMEOUT, 90);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE));
+        curl_setopt($ch, CURLOPT_TIMEOUT, 180);
+
         $response = curl_exec($ch);
         $error = curl_error($ch);
         curl_close($ch);
@@ -127,6 +121,7 @@ class WorkOrder extends Api
             ]
         ]);
     }
+
 //    /**
 //     * 图生图功能-单张图片本地测试使用
 //     * 接口地址: /sdapi/v1/img2img

+ 32 - 37
application/job/ImageToImageJob.php

@@ -38,7 +38,7 @@ class ImageToImageJob{
             $list = Db::name("text_to_image")
                 ->where('old_image_url', $old_image_url)
                 ->where('img_name', '<>', '')
-                ->where('status', 1)
+                // ->where('status', 1)
                 ->select();
 
             if (!empty($list)) {
@@ -57,8 +57,8 @@ class ImageToImageJob{
                         $data["outputDir"],
                         $row["new_image_url"],
                         $row["img_name"],
-                        679,
-                        862
+                        1024,
+                        1303
                     );
 
                     $resultText = ($result === true || $result === 1 || $result === '成功') ? '成功' : '失败或无返回';
@@ -124,69 +124,64 @@ class ImageToImageJob{
         echo "ImageJob failed: " . json_encode($data);
     }
 
+
     public function ImageToImage($fileName, $outputDirRaw, $new_image_url, $width, $height)
     {
-        // 统一路径分隔符
         $rootPath = str_replace('\\', '/', ROOT_PATH);
-
-        // 输出目录,如:ROOT/public/uploads/operate/ai/dall-e/
-        $outputDir = rtrim($rootPath . 'public/' . $outputDirRaw, '/') . '/';
-
-        // 当前日期目录,如:2025-06-16/
+        $outputDir = rtrim($rootPath . 'public/' . ltrim($outputDirRaw, '/'), '/') . '/';
         $dateDir = date('Y-m-d') . '/';
-
-        // 完整基本路径,如:ROOT/public/uploads/operate/ai/dall-e/hua/2025-06-16/
         $fullBaseDir = $outputDir . $dateDir;
 
-        // 创建输出目录,包括原图目录、目录、自定义尺寸目录
-        foreach ([$fullBaseDir, $fullBaseDir . 'newimg_679x862/', $fullBaseDir . "{$width}x{$height}/"] as $dir) {
-            if (!is_dir($dir)) {
-                mkdir($dir, 0755, true);
-            }
+        // 创建主目录和 imgtoimg 子目录
+        if (!is_dir($fullBaseDir)) {
+            mkdir($fullBaseDir, 0755, true);
         }
 
-        // 从数据库中查询原图记录
+        $imgtoimgDir = $fullBaseDir . 'imgtoimg/';
+        if (!is_dir($imgtoimgDir)) {
+            mkdir($imgtoimgDir, 0755, true);
+        }
+
+        // 查询数据库原图记录
         $record = Db::name('text_to_image')
             ->where('old_image_url', 'like', "%{$fileName}")
             ->order('id desc')
             ->find();
 
         if (!$record) {
-            return json([
-                'code' => 1,
-                'msg' => '没有找到匹配的图像记录'
-            ]);
+            return json(['code' => 1, 'msg' => '没有找到匹配的图像记录']);
         }
 
-        // 调用图生图 API
+        // 调用 AI 图生图 API
         $ai = new AIGatewayService();
-        $res = $ai->imgtoimgGptApi('', $new_image_url);
-
-        // 检查返回结果
+        // $res = $ai->imgtoimgGptApi('', $new_image_url);
+        $res = $ai->txt2imgWithControlNet('', $new_image_url);
         if (!isset($res['code']) || $res['code'] !== 0) {
-            return json([
-                'code' => 1,
-                'msg' => $res['msg'] ?? '图像生成失败'
-            ]);
+            return json(['code' => 1, 'msg' => $res['msg'] ?? '图像生成失败']);
         }
 
-        // 解码图像 base64 数据
+        // 生成保存文件路径
         $originalBaseName = pathinfo($new_image_url, PATHINFO_FILENAME);
         $finalFileName = $originalBaseName . '.png';
+        $savePath = $imgtoimgDir . $finalFileName;
 
-        // 保存到 子目录
-        $targetDir = $fullBaseDir . 'newimg_679x862/';
-        $savePath = $targetDir . $finalFileName;
-        file_put_contents($savePath, base64_decode($res['data']['url']));
+        // 写入图像文件
+        if (!file_put_contents($savePath, base64_decode($res['data']['base64']))) {
+            return json(['code' => 1, 'msg' => '图像保存失败,请检查目录权限']);
+        }
 
+        // 构造相对路径用于数据库
+        $relativeImgPath = rtrim($outputDirRaw, '/') . '/' . $dateDir . 'imgtoimg/' . $finalFileName;
+
+        // 更新数据库记录
         Db::name('text_to_image')->where('id', $record['id'])->update([
-            'imgtoimg_url' => $outputDirRaw . '/' . $dateDir . 'newimg_679x862/' . $finalFileName,
+            'imgtoimg_url' => $relativeImgPath,
             'status_name' => '图生图',
             'error_msg' => '',
             'update_time' => date('Y-m-d H:i:s')
         ]);
 
-        return '成功';
+        // 返回成功响应
+        return "成功";
     }
-
 }

+ 37 - 20
application/job/ImageToSingleJob.php

@@ -54,10 +54,8 @@ class ImageToSingleJob{
                     $result = $this->ImageToImage(
                         $data["file_name"],
                         $data["outputDir"],
-                        $row["new_image_url"],
-                        $row["img_name"],
-                        679,
-                        862
+                        $row["imgtoimg_url"],
+                        $row["img_name"]
                     );
 
                     $resultText = ($result === true || $result === 1 || $result === '成功') ? '成功' : '失败或无返回';
@@ -124,19 +122,20 @@ class ImageToSingleJob{
     }
 
 
-    public function ImageToImage($fileName, $outputDirRaw, $new_image_url, $width, $height)
+    public function ImageToImage($fileName, $outputDirRaw, $new_image_url)
     {
         $rootPath = str_replace('\\', '/', ROOT_PATH);
-        $outputDir = rtrim($rootPath . 'public/' . $outputDirRaw, '/') . '/';
+        $outputDir = rtrim($rootPath . 'public/' . ltrim($outputDirRaw, '/'), '/') . '/';
         $dateDir = date('Y-m-d') . '/';
         $fullBaseDir = $outputDir . $dateDir;
 
-        foreach ([$fullBaseDir, $fullBaseDir . 'high_definition/', $fullBaseDir . "{$width}x{$height}/"] as $dir) {
-            if (!is_dir($dir)) {
-                mkdir($dir, 0755, true);
-            }
+        // 创建 high_definition 输出目录
+        $targetDir = $fullBaseDir . 'high_definition/';
+        if (!is_dir($targetDir)) {
+            mkdir($targetDir, 0755, true);
         }
 
+        // 查询数据库记录
         $record = Db::name('text_to_image')
             ->where('old_image_url', 'like', "%{$fileName}")
             ->order('id desc')
@@ -147,24 +146,42 @@ class ImageToSingleJob{
         }
 
         $ai = new AIGatewayService();
-        $res = $ai->imgtogqGptApi($new_image_url);
+        $imgPath = $rootPath . 'public/' . ltrim($new_image_url, '/');
 
-        if (!isset($res['code']) || $res['code'] !== 0) {
-            return json(['code' => 1, 'msg' => $res['msg'] ?? '图像高清放大生成失败']);
+        if (!file_exists($imgPath)) {
+            return json(['code' => 1, 'msg' => '原图不存在:' . $imgPath]);
         }
 
-        $base64Image = $res['data']['base64_image'];
+        // 第一步:图生图放大
+        $img2imgRes = $ai->upscaleWithImg2Img('', $imgPath);
+        if (!isset($img2imgRes['code']) || $img2imgRes['code'] !== 0) {
+            return json(['code' => 1, 'msg' => $img2imgRes['msg'] ?? '图生图失败']);
+        }
+
+        // 保存中间图临时路径(可选,不强制使用)
+        $tempFileName = 'intermediate-' . time() . '.png';
+        $tempImgPathRel = $outputDirRaw . '/' . $dateDir . $tempFileName;
+        $tempImgPath = $rootPath . 'public/' . ltrim($tempImgPathRel, '/');
+        file_put_contents($tempImgPath, base64_decode($img2imgRes['data']['base64']));
+
+        // 第二步:高清超分处理
+        $hdRes = $ai->imgtogqGptApi($tempImgPathRel);
+        if (!isset($hdRes['code']) || $hdRes['code'] !== 0) {
+            return json(['code' => 1, 'msg' => $hdRes['msg'] ?? '高清图生成失败']);
+        }
+
+        // 最终高清图保存
         $originalBaseName = pathinfo($new_image_url, PATHINFO_FILENAME);
-        $finalFileName = $originalBaseName . '.png';
-        $targetDir = $fullBaseDir . 'high_definition/';
+        $finalFileName = $originalBaseName . '-' . time() . '-HD.png';
         $savePath = $targetDir . $finalFileName;
+        file_put_contents($savePath, base64_decode($hdRes['data']['base64_image']));
 
-        if (!file_put_contents($savePath, base64_decode($base64Image))) {
-            return json(['code' => 1, 'msg' => '保存图片失败']);
-        }
+        // 构造数据库中用于访问的相对路径
+        $relativePath = rtrim($outputDirRaw, '/') . '/' . $dateDir . 'high_definition/' . $finalFileName;
 
+        // 更新数据库
         Db::name('text_to_image')->where('id', $record['id'])->update([
-            'imgtoimg_url' => $outputDirRaw . '/' . $dateDir . 'high_definition/' . $finalFileName,
+            'custom_image_url' => $relativePath,
             'status_name' => '高清放大',
             'error_msg' => '',
             'update_time' => date('Y-m-d H:i:s')

+ 119 - 62
application/service/AIGatewayService.php

@@ -185,6 +185,8 @@ class AIGatewayService{
     }
 
 
+
+
     /**
      * 图生图
      * @param string $prompt 用户输入的文本提示内容
@@ -192,101 +194,156 @@ class AIGatewayService{
      * @param array $options 可选参数,可覆盖默认配置
      * @return array
      */
-    public function imgtoimgGptApi($prompt, $new_image_url, $options = [])
-    {
-        $imgPath = ROOT_PATH . 'public/' . $new_image_url;
+    public function txt2imgWithControlNet($prompt, $controlImgUrl, $options = []) {
+        $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img";
+        $headers = ['Content-Type: application/json'];
 
+        $imgPath = ROOT_PATH . 'public/' . ltrim($controlImgUrl, '/');
         if (!file_exists($imgPath)) {
-            return ['code' => 1, 'msg' => '原图不存在:' . $new_image_url];
+            return ['code' => 1, 'msg' => '图片不存在:' . $controlImgUrl];
         }
 
-        // 默认参数配置
-        $defaultParams = [
+        $imgData = file_get_contents($imgPath);
+        $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
+
+        $params = [
             'prompt' => $prompt,
-            'steps' => 15,
+            'negative_prompt' => '(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy',
+            'steps' => 20,
+            'sampler_name' => 'DPM++ 2M SDE',
             'cfg_scale' => 7,
-            'denoising_strength' => 0.2,
-            'width' => 679,
-            'height' => 862,
-            'resize_mode' => 2,
-            'sampler_name' => 'DPM++ 2M SDE Heun',
-            'seed' => -1, // 使用-1表示随机种子
-            'inpaint_full_res' => true,
-            'inpainting_fill' => 1,
+            'seed' => -1,
+            'width' => 1024,
+            'height' => 1303,
             'override_settings' => [
-                'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]',
-                'sd_vae' => 'anything-v4.5.vae.pt',
-                'CLIP_stop_at_last_layers' => 7
+                'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting',
+                'sd_vae' => 'vae-ft-mse-840000-ema-pruned',
+                'CLIP_stop_at_last_layers' => 2
             ],
-            'override_settings_restore_afterwards' => true
+            'clip_skip' => 2,
+            'alwayson_scripts' => [
+                'controlnet' => [
+                    'args' => [[
+                        'enabled' => true,
+                        'input_image' => $base64Img,
+                        'module' => 'inpaint_only+lama',
+                        'model' => 'control_v11p_sd15_inpaint_fp16 [be8bc0ed]',
+                        'weight' => 1,
+                        'resize_mode' => 'Resize and Fill',
+                        'pixel_perfect' => false,
+                        'control_mode' => 'ControlNet is more important',
+                        'starting_control_step' => 0,
+                        'ending_control_step' => 1
+                    ]]
+                ]
+            ]
         ];
 
-        // 合并用户自定义选项
-        $params = array_merge($defaultParams, $options);
-
-        // 原图 base64 编码
-        $imgData = file_get_contents($imgPath);
-        $base64Img = base64_encode($imgData);
-        $params['init_images'] = ['data:image/png;base64,' . $base64Img];
-
-        $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img";
-        $headers = ['Content-Type: application/json'];
+        if (!empty($options)) {
+            $params = array_merge($params, $options);
+        }
 
         $ch = curl_init();
         curl_setopt($ch, CURLOPT_URL, $apiUrl);
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($ch, CURLOPT_POST, true);
         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
-        curl_setopt($ch, CURLOPT_TIMEOUT, 90);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE));
+        curl_setopt($ch, CURLOPT_TIMEOUT, 180);
 
         $response = curl_exec($ch);
-
-        if (curl_errno($ch)) {
-            $error = curl_error($ch);
-            curl_close($ch);
-            return ['code' => 1, 'msg' => '请求失败:' . $error];
-        }
-
-        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $error = curl_error($ch);
         curl_close($ch);
 
-        if ($httpCode !== 200) {
-            return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode];
+        if ($error) {
+            return ['code' => 1, 'msg' => '请求失败:' . $error];
         }
 
         $data = json_decode($response, true);
-        if (json_last_error() !== JSON_ERROR_NONE) {
-            return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()];
-        }
-
         if (!isset($data['images'][0])) {
-            $errorMsg = $data['error'] ?? $data['message'] ?? '未知错误';
-            return ['code' => 1, 'msg' => 'API未返回图像数据:' . $errorMsg];
+            return ['code' => 1, 'msg' => '接口未返回图像数据'];
         }
 
         return [
             'code' => 0,
-            'msg' => '图像生成成功',
+            'msg' => '成功',
             'data' => [
-                'url' => $data['images'][0]
+                'base64' => $data['images'][0],
+                'info' => $data['info'] ?? ''
             ]
         ];
     }
 
 
-    /**
-     * 图片高清放大
-     * @param string $imageRelPath 原图相对路径(相对 public)
-     * @param array $options 可选参数,可覆盖默认放大配置
-     * @return array
-     */
-    /**
-     * 图片高清放大(不保存,返回 base64)
-     * @param string $imageRelPath 原图相对路径(相对 public)
-     * @param array $options 可选参数
-     * @return array
-     */
+
+    // 第一阶段:图生图
+    public function upscaleWithImg2Img($prompt, $imgPath)
+    {
+        if (!file_exists($imgPath)) {
+            return ['code' => 1, 'msg' => '原图不存在:' . $imgPath];
+        }
+
+        // 获取原始图像尺寸
+        [$origWidth, $origHeight] = getimagesize($imgPath);
+        if (!$origWidth || !$origHeight) {
+            return ['code' => 1, 'msg' => '无法识别图片尺寸'];
+        }
+
+        // 按2倍尺寸计算目标大小
+        $targetWidth = $origWidth * 2;
+        $targetHeight = $origHeight * 2;
+
+        // 编码图像为 base64
+        $imgData = file_get_contents($imgPath);
+        $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
+
+        // 构造参数
+        $params = [
+            'init_images' => [$base64Img],
+            'prompt' => $prompt,
+            'steps' => 20,
+            'sampler_name' => 'DPM++ 2M SDE Heun',
+            'cfg_scale' => 7,
+            'seed' => 1669863506,
+            'width' => $targetWidth,
+            'height' => $targetHeight,
+            'denoising_strength' => 0.2,
+            'clip_skip' => 2,
+            'override_settings' => [
+                'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]',
+                'sd_vae' => 'vae-ft-mse-840000-ema-pruned.safetensors',
+                'CLIP_stop_at_last_layers' => 2
+            ],
+            'override_settings_restore_afterwards' => true
+        ];
+
+        $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img";
+        $headers = ['Content-Type: application/json'];
+
+        // 发起请求
+        $ch = curl_init();
+        curl_setopt_array($ch, [
+            CURLOPT_URL => $apiUrl,
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_POST => true,
+            CURLOPT_HTTPHEADER => $headers,
+            CURLOPT_POSTFIELDS => json_encode($params),
+            CURLOPT_TIMEOUT => 180
+        ]);
+
+        $response = curl_exec($ch);
+        $error = curl_error($ch);
+        curl_close($ch);
+
+        if ($error) return ['code' => 1, 'msg' => '图生图请求失败:' . $error];
+
+        $data = json_decode($response, true);
+        if (!isset($data['images'][0])) return ['code' => 1, 'msg' => '图生图接口未返回图像'];
+
+        return ['code' => 0, 'data' => ['base64' => $data['images'][0]]];
+    }
+
+    // 第二阶段:高清超分
     public function imgtogqGptApi($imageRelPath, $options = [])
     {
         $imgPath = ROOT_PATH . 'public/' . $imageRelPath;
@@ -301,7 +358,7 @@ class AIGatewayService{
             'gfpgan_visibility' => 0,
             'codeformer_visibility' => 0,
             'codeformer_weight' => 0,
-            'upscaling_resize' => 2.45,
+            'upscaling_resize' => 1.62,
             'upscaling_crop' => true,
             'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
             'upscaler_2' => 'None',