Azure: 이미지를 아스키 문자로 변환하기③ - Custom Vision 활용하기(Postman, Swift 앱)

4 분 소요


</br> 전 편에 이어서~~
</br>

Publish 하기

Performance 탭에서 Publish 버튼을 누르면 외부에서도 예측 API를 활용할 수 있다.

24
가끔 Prediction-KeyLoading...이라고 뜨면서 오류가 날 때도 있는데, 그럴 때는 Azure에서 키 확인 어째 하다 보니까 해결 되더라
이미지 url이 있다면 위쪽, 파일이 있다면 아래쪽을 보면 된다.
저 주소로 API에 접근할 수 있고, Prediction-Key가 있어야 실행할 수 있다. Content-Type도 필요함
</br>

Postman으로 확인하기

앱을 만들기 전에, 일단 Postman으로 API를 잘 사용할 수 있는 지 확인해 보자.

25
POST 방식을 선택하고, 위에서 확인한 주소를 넣는다.
ParamsPrediction-KeyContent-Type을 적혀 있는 대로 추가해서 넣어 준다.

26
Body 탭에서 이미지 파일을 넣을 수 있다. binary를 선택하고 테스트할 이미지를 넣어 보자.
Send 버튼을 눌러 전송하면, 아래쪽과 같이 결과가 나온다!!

(와 유사한 이미지를 보내자, JSON으로 태그 40일 확률이 0.999 정도라고 잘 온다. 다른 태그들도 모두 확률 정보가 있다.
</br>

Swift로 ios 앱 만들기

서브뷰 생성

프로젝트를 새로 생성하고, ViewController.swift를 연다.
우선 변환 전의 이미지를 띄울 이미지뷰, 변환하기 위해 업로드할 수 있는 버튼, 변환 결과를 보여주는 텍스트뷰를 만들자.

    var imgV: UIImageView!
    var btnA: UIButton!
    var txtV: UITextView!
    var txt : [[Character]] = Array(repeating: Array(repeating: " ",count:20 ), count: 20)

사용한 변수들이다.
차례로 이미지뷰, 버튼, 텍스트뷰이고, 마지막 var txt는 결과를 저장할 Character 이차원 배열이다.
이차원 배열을 쓰는 이유는, 그냥 String에 결과가 나올 때마다 어펜드 해줬더니, 얘는 Async기 때문에 결과가 도착하는 시간들이 다 달라서 먼저 한 애가 늦게 오기도 하고 순서가 막 꼬여서 배열에 저장하기로 했다. 우선 공백으로 20*20을 할당해서 초기화한다.

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.imgV = UIImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
        self.imgV.image = UIImage(named: "Asset에 있는 이미지 이름")
        self.view.addSubview(imgV)
        
        self.btnA = UIButton(frame: CGRect(x: 100, y: 300, width: 100, height: 50))
        self.btnA.setTitle("Post Image", for: UIControl.State.normal)
        self.btnA.setTitleColor(.blue, for: UIControl.State.normal)
        self.btnA.addTarget(self, action: #selector(btnClicked), for: UIControl.Event.touchUpInside)
        self.view.addSubview(btnA)
        
        self.txtV = UITextView(frame: CGRect(x: 50, y: 400, width: 200, height: 200))
        self.txtV.textColor = .gray
        self.txtV.font = UIFont(name: "Courier", size: 20)
        self.view.addSubview(txtV)
    }

각각 생성한다. 위치는 일단 대충했다.
이미지뷰테 이미지를 추가하고 self.view.addSubview()로 보이게 할 수 있다.
버튼은 setTitlesetTitleColor로 “Post Image”가 파란 글씨로 보이게 했다.
addTarget에 누르면 수행할 함수를 연결한다.
텍스트뷰의 텍스트 색은 회색으로 했고, 폰트는 글자마다 같은 크기를 차지하는 Courier로 한다.

27
이미지뷰에 들어갈 이미지는 Assets에서 미리 추가했다.
</br>

이미지 크롭

ex) n = 100, k = 20 => 가로 세로 5 등분
ㅁㅁㅁㅁㅁ
ㅁㅁㅁㅁㅁ
ㅁㅁㅁㅁㅁ
ㅁㅁㅁㅁㅁ
ㅁㅁㅁㅁㅁ

image가 n*n고, k*k(k는 n의 약수)로 잘라서 아스키 코드로 표현한다고 가정하자.

그럼 이미지를 등분해서 보내 줘야 하는데, 찾아보니(https://stackoverflow.com/questions/158914/cropping-an-uiimage)

    func cropImage(image: UIImage, toRect: CGRect) -> UIImage? {
        // Cropping is available trhough CGGraphics
        let cgImage :CGImage! = image.cgImage
        let croppedCGImage: CGImage! = cgImage.cropping(to: toRect)

        return UIImage(cgImage: croppedCGImage)
    }

이런 식으로 쓰면 된다더라
원본 이미지와 CGRect를 받아서 그렇게 잘라서 이미지를 반환한다.

    @objc func btnClicked() {
        for i in 0...n/k{
            for j in 0...n/k{
                let image: UIImage = cropImage(image: self.imgV.image!, toRect: CGRect(x: i*k, y: j*k, width: k, height: k))!
                postIMG(self, image: image, ii: i, jj: j)
            }
        }
    }

따라서 버튼을 클릭했을 때 함수는 위와 같다.
widthheight는 모두 같은 한 변이 k인 정사각형일 것이고, 시작하는 위치가 달라 져야 한다.
따라서 이중포문으로, (i*k, j*k)에 접근하면 되겠더라.
그리고 postIMG() 함수를 만들고 이를 사용해 잘라진 이미지를 업로드한다.

이미지 업로드하기

    // Send Image to CustomVision API
    @objc func postIMG(_ sender: Any, image: UIImage, ii: Int, jj: Int) {
        let urlStr = "https://asciicv-prediction.cognitiveservices.azure.com/customvision/v3.0/Prediction/......"
        let urlA = URL(string: urlStr)!
        var reqA = URLRequest(url: urlA)
        reqA.httpMethod = "POST"
        reqA.addValue("......", forHTTPHeaderField: "Prediction-Key")
        reqA.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        reqA.timeoutInterval = 30.0

        /* ... */
    }

우선 Postman에서 했던 방식과 같이, url을 넣고 그 url로 URLRequest를 만든다.
거기에 httpMethodPOST로 설정하고, Prediction-KeyContent-Type을 추가한다.
언제까지고 기다릴 수 없으므로, timeoutInterval도 설정한다.

그 후 바디에 형식을 맞춰서 이미지 파일을 넣고 reqA.httpBody = body로 바디를 설정한 부분은 생략

    // Send Image to CustomVision API
    @objc func postIMG(_ sender: Any, image: UIImage, ii: Int, jj: Int) {
        /* ... */

        URLSession.shared.dataTask(with: reqA) { (dataA, respA, errA) in
            if let data = dataA, let dataString = String(data: data, encoding: .utf8) {
                // print("Response data string:\n \(dataString)")
                
                DispatchQueue.main.async {
                    let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String:Any]
                    if let arrA = json["predictions"] as? Array<Dictionary<String, Any>> {
                        var c: Character = " "
                        var prob: Float = 0.0
                        for dicA in arrA {
                            if Float(truncating: dicA["probability"] as! NSNumber) > prob {
                                prob = Float(truncating: dicA["probability"] as! NSNumber)
                                c = Character(UnicodeScalar(Int(dicA["tagName"] as! String)!)!)
                            }
                        }
                        print(ii, jj, c)
                        self.txt[ii][jj] = c
                        var s = ""
                            for i in 0...19{
                                for j in 0...19{
                                    s += String(self.txt[i][j])
                                }
                                s += "\n"
                            }
                        self.txtV.text = s
                    }
                }
            }
        }.resume()
    }

이제 URLRequest를 다 채웠으니, 보내고 답을 받아서 처리할 차례다!!
dataString에 JSON 형식으로 예측한 결과 데이터가 담겨 있다. 주석 처리한 print문을 실행하면

29
위 사진처럼 나온다.

그럼 얘를 파싱해야 한다. Array<Dictionary<String, Any>>로, 딕셔너리 어레이로 받는다.
포문을 돌면서, 확률이 제일 높은 애를 var c: Character에 저장하고, 현재 위치인 ii, jj에 맞게 txt 배열에 저장해 주고, 텍스트뷰를 업데이트해 준다.
</br>

100*100 픽셀을 10픽셀씩 나눠서 보내 보자

결과

30
오잉? 왠진 모르겠는데 돌아가 있네 이미지 크롭하고 저장하고 출력하는 데에서 어디 뒤집혀서 그런가

그래도 그건 돌려주면 되는 거고 일단 앱으로도 잘 돌아간다는 걸 확인할 수 있었다!!
회색 부분은 %로, 검은 선 부분은 @로, 좀 애매하게 잘려 간 부분은 조금 애매하긴 하고

</br>

+
35
Prediction에 잔뜩 쌓여 있는 잘린 이미지들
솔직히 사람인 나도 잘 모르겠다
</br>

++
지금 포스트 올리고 다시 검토하면서 보니까, 이미지 크롭하는 이중 포문에서 x에 i, y에 j를 넣었는데 CGRect는 x가 가로고 y가 세로라 뒤집힌 것 같다

</br>


적은 데이터로 짧게 학습해도 어느 정도는 구색은 갖추는 모습을 볼 수 있었다
로컬에 저장도 되는 걸로 안다. 로컬 프로그램으로 만들거나, 아니면 다른 사이트들처럼 웹으로 변환도 한 번 해 보거나 해볼까 그러려면 데이터를 잘 더 추가해야 할 거다
오늘은 여기로 마무리 해 보자!!
</br>

댓글남기기