AliyunOCRDriver.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. <?php
  2. namespace App\Services\OCR\Drivers;
  3. use App\Services\OCR\OCRInterface;
  4. use AlibabaCloud\SDK\Ocrapi\V20210707\Ocrapi;
  5. use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
  6. use Darabonba\OpenApi\Models\Config;
  7. use AlibabaCloud\SDK\Ocrapi\V20210707\Models\RecognizeEduPaperOcrRequest;
  8. use Illuminate\Support\Facades\Log;
  9. class AliyunOCRDriver implements OCRInterface
  10. {
  11. protected $client;
  12. public function __construct(array $config, $client = null)
  13. {
  14. if ($client) {
  15. $this->client = $client;
  16. return;
  17. }
  18. $apiConfig = new Config([
  19. 'accessKeyId' => $config['access_key_id'],
  20. 'accessKeySecret' => $config['access_key_secret'],
  21. 'endpoint' => $config['endpoint'],
  22. ]);
  23. $this->client = new Ocrapi($apiConfig);
  24. }
  25. public function recognize(string $imagePath, array $options = []): array
  26. {
  27. try {
  28. // Check if file exists
  29. if (!file_exists($imagePath)) {
  30. throw new \Exception("Image file not found: {$imagePath}");
  31. }
  32. // Get cutType from options (default: "question")
  33. $cutType = $options['cutType'] ?? 'question';
  34. $subject = $options['subject'] ?? 'Math';
  35. // Read file content
  36. $fileStream = fopen($imagePath, 'rb');
  37. $stream = \GuzzleHttp\Psr7\Utils::streamFor($fileStream);
  38. $request = new \AlibabaCloud\SDK\Ocrapi\V20210707\Models\RecognizeEduPaperCutRequest([
  39. 'body' => $stream,
  40. 'cutType' => $cutType,
  41. 'imageType' => 'photo',
  42. 'subject' => $subject,
  43. 'outputOricoord' => false
  44. ]);
  45. $runtime = new RuntimeOptions([]);
  46. // Call Aliyun API
  47. $response = $this->client->recognizeEduPaperCutWithOptions($request, $runtime);
  48. // Close stream
  49. if (is_resource($fileStream)) {
  50. fclose($fileStream);
  51. }
  52. // Parse response
  53. $body = json_decode(json_encode($response->body), true);
  54. // Detailed logging
  55. Log::info('Aliyun EduPaperCut Full Response', [
  56. 'cutType' => $cutType,
  57. 'has_data' => isset($body['data']),
  58. 'request_id' => $body['requestId'] ?? null,
  59. 'code' => $body['code'] ?? null,
  60. 'message' => $body['message'] ?? null,
  61. 'body_keys' => array_keys($body ?? [])
  62. ]);
  63. // Log raw data if exists
  64. if (isset($body['data'])) {
  65. $dataPreview = is_string($body['data'])
  66. ? substr($body['data'], 0, 500)
  67. : json_encode($body['data']);
  68. Log::info('Aliyun Data Preview', ['data' => $dataPreview]);
  69. }
  70. // Extract data from Aliyun response
  71. $questions = [];
  72. if (isset($body['data'])) {
  73. // The data field is a JSON string
  74. $data = is_string($body['data']) ? json_decode($body['data'], true) : $body['data'];
  75. // Extract page_list -> subject_list OR answer_list
  76. if (isset($data['page_list']) && is_array($data['page_list'])) {
  77. foreach ($data['page_list'] as $page) {
  78. // Determine which list to use based on cutType
  79. $itemList = null;
  80. if ($cutType === 'answer' && isset($page['answer_list'])) {
  81. $itemList = $page['answer_list'];
  82. } elseif (isset($page['subject_list'])) {
  83. $itemList = $page['subject_list'];
  84. }
  85. if ($itemList && is_array($itemList)) {
  86. foreach ($itemList as $item) {
  87. // Extract question/answer data
  88. $questionNumber = null;
  89. if (isset($item['ids']) && is_array($item['ids']) && !empty($item['ids'])) {
  90. $questionNumber = $item['ids'][0];
  91. } else {
  92. $questionNumber = count($questions) + 1;
  93. }
  94. // Get text - if not provided, build from prism_wordsInfo
  95. $text = $item['text'] ?? '';
  96. if (empty($text) && isset($item['prism_wordsInfo']) && is_array($item['prism_wordsInfo'])) {
  97. $words = [];
  98. foreach ($item['prism_wordsInfo'] as $wordInfo) {
  99. if (isset($wordInfo['word'])) {
  100. $words[] = $wordInfo['word'];
  101. }
  102. }
  103. $text = implode('', $words);
  104. }
  105. // Calculate confidence from prism_wordsInfo
  106. $confidence = 0.0;
  107. if (isset($item['prism_wordsInfo']) && is_array($item['prism_wordsInfo'])) {
  108. $totalProb = 0;
  109. $count = 0;
  110. foreach ($item['prism_wordsInfo'] as $wordInfo) {
  111. if (isset($wordInfo['prob'])) {
  112. $totalProb += $wordInfo['prob'];
  113. $count++;
  114. }
  115. }
  116. $confidence = $count > 0 ? ($totalProb / $count) / 100 : 0.0;
  117. }
  118. $questions[] = [
  119. 'question_number' => $questionNumber,
  120. 'content' => $text,
  121. 'cut_type' => $cutType,
  122. 'confidence' => $confidence,
  123. 'raw_data' => $item
  124. ];
  125. }
  126. }
  127. }
  128. }
  129. }
  130. return [
  131. 'raw' => $body,
  132. 'questions' => $questions,
  133. 'cut_type' => $cutType
  134. ];
  135. } catch (\Exception $e) {
  136. Log::error('Aliyun OCR Error', [
  137. 'message' => $e->getMessage(),
  138. 'trace' => $e->getTraceAsString(),
  139. ]);
  140. throw $e;
  141. }
  142. }
  143. }