From a0e1391528bdb33217c339c18b3434ef027f26cc Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:01:36 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Feat:=20=EC=96=BC=EA=B5=B4=20=EC=98=81?= =?UTF-8?q?=EC=97=AD=20=EC=A2=8C=ED=91=9C=20dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 추후 감정 분석 시 얼굴 영역 crop 시 사용 --- src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java diff --git a/src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java b/src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java new file mode 100644 index 0000000..7fcdb2a --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/BoundingBox.java @@ -0,0 +1,3 @@ +package com.bamboo.log.emotion.dto; + +public record BoundingBox(double x1, double y1, double x2, double y2) {} From 8ba6864561715d67efaa006b7b45161356919b08 Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:02:55 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Fix:=20=ED=91=9C=EC=A0=95=20=EB=B6=84?= =?UTF-8?q?=EC=84=9D=20=EC=9C=84=ED=95=9C=20response=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좌표 값 고려하여 List으로 변경 --- .../com/bamboo/log/emotion/dto/FaceDetectionResponse.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java b/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java index f9a2b09..16551a0 100644 --- a/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java +++ b/src/main/java/com/bamboo/log/emotion/dto/FaceDetectionResponse.java @@ -2,12 +2,14 @@ import org.springframework.http.HttpStatus; +import java.util.List; + public record FaceDetectionResponse( int statusCode, String statusMessage, - String message + List faceBox ) { - public FaceDetectionResponse(HttpStatus httpStatus, String message) { - this(httpStatus.value(), httpStatus.name(), message); + public FaceDetectionResponse(HttpStatus httpStatus, List faceBox) { + this(httpStatus.value(), httpStatus.name(), faceBox); } } \ No newline at end of file From 62ffc96d23c47a7e5cc3178da38698df17e66bfd Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:03:59 +0900 Subject: [PATCH 3/9] =?UTF-8?q?Fix:=20=EC=88=98=EC=A0=95=EB=90=9C=20respon?= =?UTF-8?q?se=20=EB=A7=9E=EA=B2=8C=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HttpStatus와 함께 얼굴 영역 좌표 값 리턴 --- .../impl/FaceDetectionServiceImpl.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java b/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java index 47e6d8a..0175ac0 100644 --- a/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java +++ b/src/main/java/com/bamboo/log/emotion/service/impl/FaceDetectionServiceImpl.java @@ -1,5 +1,6 @@ package com.bamboo.log.emotion.service.impl; +import com.bamboo.log.emotion.dto.BoundingBox; import com.bamboo.log.emotion.dto.FaceDetectionResponse; import com.bamboo.log.emotion.service.FaceDetectionService; import com.fasterxml.jackson.databind.JsonNode; @@ -13,6 +14,8 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @Slf4j @Service @@ -31,7 +34,7 @@ public class FaceDetectionServiceImpl implements FaceDetectionService { public FaceDetectionResponse detectFace(MultipartFile image) { if (image == null || image.isEmpty()) { log.error("파일이 전달되지 않음."); - return new FaceDetectionResponse(HttpStatus.BAD_REQUEST, "파일이 전달되지 않았습니다."); + return new FaceDetectionResponse(HttpStatus.BAD_REQUEST, List.of()); } log.info("파일 이름: {}", image.getOriginalFilename()); log.info("파일 크기: {} bytes", image.getSize()); @@ -61,7 +64,7 @@ public FaceDetectionResponse detectFace(MultipartFile image) { log.info("API 응답 메시지: {}", response.message()); if (!response.isSuccessful()) { - return new FaceDetectionResponse(HttpStatus.valueOf(response.code()), "API 요청 실패: " + response.message()); + return new FaceDetectionResponse(HttpStatus.valueOf(response.code()), List.of()); } String responseBody = response.body().string(); @@ -72,18 +75,27 @@ public FaceDetectionResponse detectFace(MultipartFile image) { JsonNode results = jsonNode.get("results"); // 얼굴 인식 여부 확인 - if (results != null && results.isArray() && results.size() > 0) { + List boxList = new ArrayList<>(); + if (results != null && results.isArray()) { for (JsonNode result : results) { if ("face".equals(result.get("name").asText())) { - return new FaceDetectionResponse(HttpStatus.OK, responseBody); + JsonNode boxNode = result.get("box"); + if (boxNode != null) { + boxList.add(new BoundingBox( + boxNode.get("x1").asDouble(), + boxNode.get("y1").asDouble(), + boxNode.get("x2").asDouble(), + boxNode.get("y2").asDouble() + )); + } } } } - return new FaceDetectionResponse(HttpStatus.NOT_FOUND, "얼굴이 인식되지 않았습니다."); + return new FaceDetectionResponse(HttpStatus.OK, boxList); } } catch (IOException e) { log.error("API 요청 중 예외 발생", e); - return new FaceDetectionResponse(HttpStatus.INTERNAL_SERVER_ERROR, "API 요청 중 오류 발생: " + e.getMessage()); + return new FaceDetectionResponse(HttpStatus.INTERNAL_SERVER_ERROR, List.of()); } } From 06f9be4b765bffff51aa9fd232db07ee110fc83e Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:05:32 +0900 Subject: [PATCH 4/9] =?UTF-8?q?Feat:=20EmotionType=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버 오류 시, NONE 리턴 --- .../com/bamboo/log/emotion/domain/EmotionType.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/bamboo/log/emotion/domain/EmotionType.java diff --git a/src/main/java/com/bamboo/log/emotion/domain/EmotionType.java b/src/main/java/com/bamboo/log/emotion/domain/EmotionType.java new file mode 100644 index 0000000..8fbbe0e --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/domain/EmotionType.java @@ -0,0 +1,10 @@ +package com.bamboo.log.emotion.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EmotionType { + ANGRY, HAPPY, NEUTRAL, SAD, NONE ; +} \ No newline at end of file From d1951ac636a7ebaee97f3a716fa6c1599ec4c558 Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:07:03 +0900 Subject: [PATCH 5/9] =?UTF-8?q?Settings:=20FastAPI=20URL=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 17527e6..3ab1365 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -39,4 +39,7 @@ elice: url: face: ${FACE_URL} img: ${IMG_URL} - chat: ${CHAT_URL} \ No newline at end of file + chat: ${CHAT_URL} +emotion: + api: + url: ${EMOTION_URL} \ No newline at end of file From 6ee757074daa7fcc319a86bd000858fb7b82fefc Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:07:26 +0900 Subject: [PATCH 6/9] =?UTF-8?q?Feat:=20=EA=B0=90=EC=A0=95=20=EB=B6=84?= =?UTF-8?q?=EC=84=9D=20req/res=20DTO=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java | 6 ++++++ .../bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java create mode 100644 src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java diff --git a/src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java b/src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java new file mode 100644 index 0000000..cf925a5 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/EmotionAnalysisResponse.java @@ -0,0 +1,6 @@ +package com.bamboo.log.emotion.dto; + +import com.bamboo.log.emotion.domain.EmotionType; +import org.springframework.http.HttpStatus; + +public record EmotionAnalysisResponse (HttpStatus status, EmotionType emotion) {} diff --git a/src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java b/src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java new file mode 100644 index 0000000..7961010 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/dto/req/EmotionAnalysisRequest.java @@ -0,0 +1,6 @@ +package com.bamboo.log.emotion.dto.req; + +import com.bamboo.log.emotion.dto.BoundingBox; +import org.springframework.web.multipart.MultipartFile; + +public record EmotionAnalysisRequest(MultipartFile image, BoundingBox faceBox) {} From e1205d13667806d0e8f47b134f88aea59302d2bb Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:08:40 +0900 Subject: [PATCH 7/9] =?UTF-8?q?Feat:=20=EA=B0=90=EC=A0=95=20=EB=B6=84?= =?UTF-8?q?=EC=84=9D(=EC=9A=94=EC=B2=AD)=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FASTAPI 요청을 통해 감정 분석 - HttpStatus code 와 enum 타입의 감정 결과 return --- .../service/EmotionAnalysisService.java | 8 ++ .../impl/EmotionAnalysisServiceImpl.java | 84 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java create mode 100644 src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java diff --git a/src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java b/src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java new file mode 100644 index 0000000..7e4300d --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/service/EmotionAnalysisService.java @@ -0,0 +1,8 @@ +package com.bamboo.log.emotion.service; + +import com.bamboo.log.emotion.dto.EmotionAnalysisResponse; +import com.bamboo.log.emotion.dto.req.EmotionAnalysisRequest; + +public interface EmotionAnalysisService { + EmotionAnalysisResponse analyzeEmotion(EmotionAnalysisRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java b/src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java new file mode 100644 index 0000000..c707b8e --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/service/impl/EmotionAnalysisServiceImpl.java @@ -0,0 +1,84 @@ +package com.bamboo.log.emotion.service.impl; + +import com.bamboo.log.emotion.domain.EmotionType; +import com.bamboo.log.emotion.dto.BoundingBox; +import com.bamboo.log.emotion.dto.EmotionAnalysisResponse; +import com.bamboo.log.emotion.dto.req.EmotionAnalysisRequest; +import com.bamboo.log.emotion.service.EmotionAnalysisService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@Slf4j +@Service +@RequiredArgsConstructor +public class EmotionAnalysisServiceImpl implements EmotionAnalysisService { + @Value("${emotion.api.url}") + private String emotionApiUrl; + + private final OkHttpClient client = new OkHttpClient(); + + @Override + public EmotionAnalysisResponse analyzeEmotion(EmotionAnalysisRequest request) { + MultipartFile image = request.image(); + BoundingBox faceBox = request.faceBox(); + + log.info("Received EmotionAnalysisRequest: {}", request); + log.info("Received BoundingBox: {}", (faceBox != null) ? faceBox.toString() : "NULL"); + + if (faceBox == null) { + log.warn("얼굴 영역이 감지되지 않음. 감정 분석 불가능"); + return new EmotionAnalysisResponse(HttpStatus.OK, EmotionType.NONE); + } + + EmotionAnalysisResponse response = callFastAPI(image, request); + + log.info("Received response: {}", response); + return response; + } + + private EmotionAnalysisResponse callFastAPI(MultipartFile image, EmotionAnalysisRequest request) { + try { + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("image", image.getOriginalFilename(), + RequestBody.create(image.getBytes(), MediaType.parse(image.getContentType()))) + .addFormDataPart("x1", String.valueOf(request.faceBox().x1())) + .addFormDataPart("y1", String.valueOf(request.faceBox().y1())) + .addFormDataPart("x2", String.valueOf(request.faceBox().x2())) + .addFormDataPart("y2", String.valueOf(request.faceBox().y2())) + .build(); + + Request fastApiRequest = new Request.Builder() + .url(emotionApiUrl) + .post(requestBody) + .addHeader("accept", "application/json") + .build(); + + try (Response response = client.newCall(fastApiRequest).execute()) { + if (!response.isSuccessful()) { + return new EmotionAnalysisResponse(HttpStatus.valueOf(response.code()), EmotionType.NONE); + } + + String responseBody = response.body().string(); + JsonNode jsonNode = new ObjectMapper().readTree(responseBody); + String emotionString = jsonNode.get(0).get("emotion").asText(); + EmotionType emotionType = EmotionType.valueOf(emotionString.toUpperCase()); + + log.info("Received response about api: {}", response); + return new EmotionAnalysisResponse(HttpStatus.OK, emotionType); + } + } catch (IOException e) { + log.error("FASTAPI 호출 중 오류 발생", e); + return new EmotionAnalysisResponse(HttpStatus.INTERNAL_SERVER_ERROR, EmotionType.NONE); + } + } +} \ No newline at end of file From 2bab740ffa958df0fbcbd6a6b3a8600dbf7c9390 Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:12:09 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Refactor:=20=EC=96=BC=EA=B5=B4=20=EC=9D=B8?= =?UTF-8?q?=EC=8B=9D=20API=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 감정 분석 API 구분을 위한 api 변경 --- .../bamboo/log/emotion/controller/FaceDetectController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java b/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java index fee5189..62fd28f 100644 --- a/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java +++ b/src/main/java/com/bamboo/log/emotion/controller/FaceDetectController.java @@ -15,14 +15,14 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/emotion") -@Tag(name = "Face Detection", description = "얼굴 인식 관련 API") +@RequestMapping("/api/face") +@Tag(name = "Face Detection", description = "얼굴 인식 API") public class FaceDetectController { private final FaceDetectionService faceDetectionService; @PostMapping("/detect") @Operation( - summary = "얼굴 인식 API", + summary = "얼굴 인식", description = "이미지를 업로드하면 얼굴 인식 여부를 반환합니다." ) public ResponseEntity detectFace(@RequestParam("image") MultipartFile image) { From c05ed162872bdfb09212000b33eb1dde11f0c27e Mon Sep 17 00:00:00 2001 From: qhdudedi Date: Wed, 26 Feb 2025 23:16:51 +0900 Subject: [PATCH 9/9] =?UTF-8?q?Feat:=20=EA=B0=90=EC=A0=95=20=EB=B6=84?= =?UTF-8?q?=EC=84=9D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../emotion/controller/EmotionController.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/com/bamboo/log/emotion/controller/EmotionController.java diff --git a/src/main/java/com/bamboo/log/emotion/controller/EmotionController.java b/src/main/java/com/bamboo/log/emotion/controller/EmotionController.java new file mode 100644 index 0000000..b801e59 --- /dev/null +++ b/src/main/java/com/bamboo/log/emotion/controller/EmotionController.java @@ -0,0 +1,50 @@ +package com.bamboo.log.emotion.controller; + +import com.bamboo.log.emotion.dto.BoundingBox; +import com.bamboo.log.emotion.dto.EmotionAnalysisResponse; +import com.bamboo.log.emotion.dto.req.EmotionAnalysisRequest; +import com.bamboo.log.emotion.service.EmotionAnalysisService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/emotion") +@RequiredArgsConstructor +@Tag(name = "Emotion Analysis", description = "얼굴 감정(표정)분석 API") +public class EmotionController { + + private final EmotionAnalysisService emotionAnalysisService; + + @PostMapping(value = "/result", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation( + summary = "감정 분석", + description = "반환받은 얼굴 영역 좌표와 이미지를 분석하여 감정을 반환합니다." + ) + public ResponseEntity analyzeEmotion( + + @RequestPart("image") + @Parameter(description = "감정 분석할 얼굴 이미지", required = true) + MultipartFile image, + + @RequestPart("faceBox") + @Parameter(description = "얼굴 영역 좌표 (JSON 형식)", required = true, example = "{\"x1\": 12.34, \"y1\": 56.78, \"x2\": 123.456, \"y2\": 789.87}") + BoundingBox faceBox) { + + EmotionAnalysisRequest request = new EmotionAnalysisRequest(image, faceBox); + EmotionAnalysisResponse response = emotionAnalysisService.analyzeEmotion(request); + + HttpStatus status = HttpStatus.resolve(response.status().value()); + if (status == null) { + status = HttpStatus.INTERNAL_SERVER_ERROR; + } + + return ResponseEntity.status(status).body(response); + } +}