간단한 프로젝트 진행을 하면서 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결과를 저장하도록 했다.
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으로 인식함 ㅋㅋ)
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 사용설명서
'Language > Go' 카테고리의 다른 글
[Go] defer, panic, recover?? (1) | 2020.01.26 |
---|---|
[Go] Go 사용하기(코드 구조, package) (0) | 2020.01.23 |
[Go] - web programming : postgresSQL연결, CRUD사용해보기 (1) | 2020.01.20 |
[Go] - web programming : csv, gob 패키지 다루기 (0) | 2020.01.20 |
[Go] - web programming : 상황 인지, XSS 공격 막기 (0) | 2020.01.16 |