본문 바로가기

Django and Mongo

Django REST API & Djongo - 4. CustomField & Embedded Model

본 작성자는 아직 1년차도 못채운 햇병아리 코더입니다.

 

(사내에 장고를 다루는 사람이 저 혼자라서) 기술한 내용중에 틀린 내용이 있을 수도 있습니다.

 

예제랍시고 올려놓은 코드가 아주 허접할 수도 있습니다.

 

미숙한 제게 조언을 주실 수 있다면 댓글 부탁드립니다. 겸허히 배우겠습니다.

 

개발 대상 사양은 모두 아래를 공통적으로 사용합니다.

[개발 OS : Windows10]

[설치 OS : CentOS 8 with gnomeUI]

[Python: 3.8]

[Mongodb : 4.4.1]

[PostgreSQL : 12.4]

[Elasticsearch : 7.9.3]

[Tensorflow : 2.4.1]

[scikit-learn : 0.23.2]

------------------------------------------------------------------

 

오늘 다뤄볼 주제는 Custom Feild 와 Embeded Field 입니다.

 

심도있게 다뤄볼 것은 아니고, 이미지 업로드 요청이 들어오면 자동으로 OCR 이미지 추출을 한 뒤 해당 내용을 새로운 필드로 지정하여 업로드하는 예제를 다룰 겁니다.

 

OCR 관련된 내용은

triple-f-coding.tistory.com/6

 

OCR - Pytesseract 를 이용한 OCR

이 내용은 예전에 딥러닝 스터디를 진행하면서 익힌 내용입니다. 근데 딥러닝은 안썼다. www.pyimagesearch.com/2020/05/25/tesseract-ocr-text-localization-and-detection/ Tesseract OCR: Text localization an..

triple-f-coding.tistory.com

위 링크에 올려두었습니다.

 

 

 

당연히 수만가지 방법이 있겠지만,

1. Serializer를 커스텀 하는 방법

2. ModelViewSets의 create 메소드를 오버라이딩 하는 방법.

이 2가지만 다루겠습니다.

 

 

 

 

1. Serializer를 커스텀 하는 방법.

=> 여기에도 2가지 방법이 있습니다.

1. Custom Field를 작성하는 방법.

2. Custom Serializer를 작성하하는 방법.

 

 

1-1. Custom Field를 작성하는 방법.

 

CustomField 는 간단하게 2개의 메서드를 오버라이딩 하면 됩니다.

 

to_internal_valueto_representation이 그것입니다.

 

to_internal_value는 내부 저장용도로, to_representation은 응답 용도로 사용됩니다.

 

다만 이 과정에서 조금 문제가 있는데, 기존 코드는 OpenCV의 파라미터로 이미지 경로를 넘겨주었다면, 이 경우에는 파일 자체가 "InMemoryUploadedFile" 이라서 경로를 가지고 있지 않다는 점입니다.

 

def tesseract_ocr_extract_from_file(image_file):
    image = cv2.imdecode(np.frombuffer(image_file.read(), np.uint8), cv2.COLOR_BGR2RGB)
    rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    results = pytesseract.image_to_data(rgb, output_type=pytesseract.Output.DICT)
    return_list = [{
            "left": results["left"][i],
            "top": results["top"][i],
            "width": results["width"][i],
            "height": results["height"][i],
            "text": results["text"][i],
            "confidence": int(results["conf"][i])
            } for i in range(len(results["text"]))]
    return return_list

아주 살짝 함수를 수정해 줍니다. 조금 복잡해 보이지만 결국 하고자 하는 일은 똑같습니다.

 

추가로 DRF 에서의 커스텀 필드는 오직 1개의 입력 데이터와 1개의 출력 데이터에 대한 관계만 정의하게 됩니다.

예를 들어 image 라는 필드를 커스텀 필드로 지정하면, image라는 입력값만을 가지고 image의 출력을 만들어내야 하는 것이죠.

 

Djongo의 Embedded 필드를 사용하면 BSON에 depth를 주어서 계층화된 데이터를 표현할 수 있게 됩니다.

 

사용자는 이미지파일 하나만을 업로드하지만, API에서는 파일의 이름을 추출하고, 파일도 저장하고, 거기에 파일내의 텍스트가 있다면 OCR을 통해서 그 텍스트까지 추출해서 저장해야 합니다.

 

이때 사용함직한 것이 Embedded Field 입니다.

 

class OCRContextField(models.Model):
    left = models.FloatField()
    top = models.FloatField()
    width = models.FloatField()
    height = models.FloatField()
    text = models.TextField()
    confidence = models.IntegerField()

    class Meta:
        abstract = True


class ImageEmbeddedField(models.Model):
    image_title = models.TextField(null=True, default="a.jpg")
    image_url = models.ImageField(null=False, upload_to="uploaded_pictures")
    image_context = models.ArrayField(model_container=OCRContextField)

    class Meta:
        abstract = True


class UploadedImages(models.Model):
    _id = models.ObjectIdField()
    image = models.EmbeddedField(model_container=ImageEmbeddedField, null=False)
    created_date = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "uploaded_images"

pytesseract는 결과를 List[Dict] 형태로 돌려주기 때문에 이것을 그대로 담을 필드가 필요합니다.

이 필드를 Embedded Field 로 받아주고, 그걸 DB에 저장하고 돌려주는 코드를 만들면 됩니다.

 

class OCRImageField(serializers.ImageField):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def to_internal_value(self, data):
        image_url = super().to_internal_value(data)
        image_title = data.name
        image_context = tesseract_ocr_extract_from_file(data)
        return {
            "image_title": image_title,
            "image_url": image_url,
            "image_context": image_context
        }

    def to_representation(self, value):
        value["image_url"] = super().to_representation(value["image_url"])
        return value


class UploadedImageSerializer(serializers.ModelSerializer):
    image = OCRImageField()

    class Meta:
        model = UploadedImages
        fields = "__all__"

 

Embedded Field 는 간단히 Dict를 받으면 그걸 BSON으로 변환해줍니다.

 

to_internal_value에서는 간단히 원하는 데이터를 모두 Dict에 담아 돌려주기만 하면 됩니다!

 

to_representation 에서는 원래 ImageField 였던 "image_url"을 원래 하던 처리대로, 즉 ImageField의 to_representation을 거쳐서 돌려주면 됩니다.

 

 

POST로 이미지 하나를 쏜 다음에....

 

이제 GET으로 호출하면 제대로 분석결과를 돌려준다.

 

다른 방법론은 다음 게시글에 이어가겠습니다.

 

 

 

----------------------------------------

 

참고

github.com/KimDoKy/DjangoRestFramework-Tutorial/blob/master/doc/Django%20REST%20Framework%20-%2011.%20Serializer%20relations.md

 

KimDoKy/DjangoRestFramework-Tutorial

DjangoRestFramework-Tutorial. Contribute to KimDoKy/DjangoRestFramework-Tutorial development by creating an account on GitHub.

github.com

 

www.django-rest-framework.org/api-guide/fields/#raising-validation-errors

 

Serializer fields - Django REST framework

 

www.django-rest-framework.org

stackoverflow.com/questions/27517688/can-an-uploaded-image-be-loaded-directly-by-cv2

 

Can an uploaded image be loaded directly by cv2?

I have an uploaded file in memory. I want to manipulate the file with cv2. Currently, I write the file to disk then read it with cv2. How can I skip writing the file and load it directly with cv...

stackoverflow.com