본문 바로가기

Language/Go

[Go] 이미지 업로드 + naver cloud platform OCR 써보기

간단한 프로젝트 진행을 하면서 Go로 이미지 업로드 서버 + 업로드한 이미지를 통해 OCR 적용 해보았다.

 

1. 이미지 업로드 서버 만들기

 - exif 태그보고 rotate하기

 - io을 다뤄서 파일 저장하기

 

2. OCR 써보기

 - JSON parsing

 - reqeust하기

 

 

1. 이미지 업로드 서버 만들기

- client side - index.html

 기능 구현을 위해 post전송을 할 form정도만 구현해놓았다.. form은 post, multipart/form-data를 사용했다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go Web Programming</title>
</head>
<body>
<div>
    {{.}}
</div>
<div>
    <img width="400px" height="300px" id="test" alt="" src="">
</div>
<form action="http://localhost:8080/index" method="post" enctype="multipart/form-data">
    <input type="file" name="sample">
    <input type="submit">
</form>
</body>
</html>

 

아직 조잡함 ㅎ

 

 - server side

  main.go가 메인 실행파일, utils안에 코드를 분할, images에 이미지저장, json에 ocr결과를 저장하도록 했다.

server쪽 디렉토리 구성

 

method에 따라 기능을 분리하고..

func index(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
		case "POST" :
			OCRtest(w, r)
		case "GET":
			t, _ := template.ParseFiles("template/index.html")
			_ = t.Execute(w, "Upload Image")
	}
}

 

* 이미지를 그냥 저장하니깐 파일이 돌아가서 저장된다. -> exif 값을 읽고 직접 돌려서 저장해주자

* 이미지의 파일 포맷을 찾고, goexif 서드파티모듈로 exif을 디코딩 했다.

* 여기서 io reader를 한번 쓰고, 다시 못쓰는 상황이 계속 발생했다..

* 동기화 문제인줄 알고 sync.Mutex랑 고루틴을 막 써봤는데 문제는 버퍼에 있었다.

* io.Reader를 한 번 읽고 다시 읽으려면 Seek 함수를 통해 버퍼를 움직여줘야 한다.

func OCRtest(w http.ResponseWriter, r *http.Request) {
	ch := make(chan string)
	var orientation *tiff.Tag

	file, fileHeader, _ := r.FormFile("sample")
	defer file.Close()
	split := strings.Split(fileHeader.Filename, ".")
	suffix := split[len(split) - 1]

	go func() {
		x, _ := exif.Decode(file)
		_, _ = file.Seek(0, 0)
		orientation, _ = x.Get(exif.Orientation)
		ch <- orientation.String()
	}()
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

* orientation을 찾았으면 그값과 suffix를 통해 방향을 바꿔주고 다시 저장해주자.

* OCR을 사용하기위해 이미지를 base64 인코딩을 진행해주자.

* 이미지 저장의 경우 imaging 서드파티 모듈에서 img를 Save해주는 함수를 지원해주었다.

	go func() {
		val := <- ch
		img := utils.ConvertImage(file, suffix, val)
		imaging.Save(img, "./images/"+fileHeader.Filename)
		bytesData, _ := ioutil.ReadFile("./images/" + fileHeader.Filename)
		encData := base64.StdEncoding.EncodeToString(bytesData)
		utils.RequsetOCR(encData)
	}()

 

* 이미지 방향 바꿔주는 함수는 아래와 같다. imaging 서드파티모듈를 사용했다.

* orientation 값이 1~8이 있는데 exif 공식문서에 친절하게 나와 있었다. (ref: http://www.exif.org/Exif2-2.PDF )

func ConvertImage(f io.Reader, suffix string, o string) *image.NRGBA {
	var img image.Image
	var err error

	switch suffix {
	case "jpg":
		img, err = jpeg.Decode(f)
		println("convert file to jpg image")
		if err != nil {
			return nil
		}
	case "png":
		img, err = png.Decode(f)
		println("convert file to png image")
		if err != nil {
			return nil
		}
	case "gif":
		img, err = gif.Decode(f)
		println("convert file to gif image")
		if err != nil {
			return nil
		}
	}
	switch o {
	case "1":
		return imaging.Clone(img)
	case "2":
		return imaging.FlipV(img)
	case "3":
		return imaging.Rotate180(img)
	case "4":
		return imaging.Rotate180(imaging.FlipV(img))
	case "5":
		return imaging.Rotate270(imaging.FlipV(img))
	case "6":
		return imaging.Rotate270(img)
	case "7":
		return imaging.Rotate90(imaging.FlipV(img))
	case "8":
		return imaging.Rotate90(img)
	}
	return imaging.Clone(img)
}

 

 

2. OCR 써보기

 

* ocr 쓰는 법은 사용설명서에 잘 나와있었따. (ref : https://apidocs.ncloud.com/ko/ai-application-service/ocr/ocr/)

* 위의 링크대로 요청 바디를 만들어보자.

 

* Go json <-> struct 가 잘 되어있어서 간편하게 사용할 수 있었다.

* tag를 이용하여 json으로 변환될 때 key값과, 값이 없을때 생략해줄 omitempty 태그를 적어주자

* 공식문서에 images 부분에서 Data, Url 1개값만 있어야 한다고 한다. 이때 Url의 경우 정규표현식이 조금 빡세서 나는 그냥 base64로 이미지를 인코딩해서 data에 넣어주었다.

type OCRRequest struct {
	Version   string    `json:"version"`
	RequestId string    `json:"requestId"`
	Timestamp string    `json:"timestamp"`
	Lang      string    `json:"lang, omitempty"`
	Images    []OCRImage `json:"images"`
}

type OCRImage struct {
	Format     string   `json:"format"`
	Data       string   `json:"data,omitempty"`
	Url		   string   `json:"url,omitempty"`
	Name       string   `json:"name"`
	TemplateId []string `json:"templateId, omitempty"`
}

 

* reqeust하는 함수다. base64로 인코딩 된 이미지 파일의 데이터를 보내주면 값을 요청한다.

* uuid의 경우 google/uuid 서드파티모듈을 사용했다. (네이버 예제코드처럼 나도 그냥 랜덤하게 생성해서 보냈다.)

* NewRequest함수로 새로운 request를 만들고, 설명서에 나온대로 헤더와 메소드, struct로 parse한 json파일을 넣어주자.

func RequsetOCR(data string) {
	ocrURl := ""
	ocrSecretKey := ""
	timestamp := int(time.Now().Unix())
	ocrImages := make([]OCRImage, 1)
	ocrImages[0] = OCRImage {
		Format:     "jpg",
		Data:		data,
		Name:       "sample",
	}
	ocrRequest := OCRRequest{
		Version:   "V1",
		RequestId: uuid.New().String(),
		Timestamp: strconv.Itoa(timestamp),
		Lang:      "ko",
		Images:    ocrImages,
	}
	doc, _ := json.Marshal(ocrRequest)
	println(string(doc))
	req, _ := http.NewRequest("POST", ocrURl, strings.NewReader(string(doc)))
	req.Header.Add("Content-type", "application/json")
	req.Header.Add("X-OCR-SECRET", ocrSecretKey)

	client := &http.Client{}
	res, _ := client.Do(req)
	resBody, _ := ioutil.ReadAll(res.Body)
	println(string(resBody))
}

 

3. 결과

 

* 내 글씨가 안좋은지 결과가 안좋았다.. 글씨좀 이쁘게 쓰고 다시 해봐야겠다. (3개를 37F1으로 인식함 ㅋㅋ)

 

입력 이미지..
출력 json결과 inferText만 보이게 했다.

 

이미지가 orientation을 읽고 잘 돌아갔다.

 

4. 나중에 할 것

 

* response 작성해서 응답보내기

* 쿠팡 API써보기, 챗봇 API써보기

* db연동하고, 고루틴 다시 공부하기

 

5. 레퍼런스

- https://golang.org/pkg/io/ : go io 패키지 doc

- http://www.exif.org/Exif2-2.PDF : exif 공식문서

- https://godoc.org/github.com/disintegration/imaging : 이미지 변환할 때 쓴 서드파티 모듈

- https://godoc.org/github.com/google/uuid : uuid 만들때 쓴 서드파티 모듈

- https://godoc.org/github.com/rwcarlsen/goexif/exif : exif 읽을 때 쓴 서드파티 모듈

- 책 "가장 빨리 만나는 Go 언어" : 기본기 설명이 잘되어있다.

- https://apidocs.ncloud.com/ko/ai-application-service/ocr/ocr/ : 네이버 클라우드 플랫폼 ocr 사용설명서