Azure: 이미지를 아스키 문자로 변환하기③ - Custom Vision 활용하기(Postman, Swift 앱)
</br>
전 편에 이어서~~
</br>
Publish 하기
Performance
탭에서 Publish
버튼을 누르면 외부에서도 예측 API를 활용할 수 있다.
가끔 Prediction-Key
가 Loading...
이라고 뜨면서 오류가 날 때도 있는데, 그럴 때는 Azure에서 키 확인 어째 하다 보니까 해결 되더라
이미지 url이 있다면 위쪽, 파일이 있다면 아래쪽을 보면 된다.
저 주소로 API에 접근할 수 있고, Prediction-Key
가 있어야 실행할 수 있다. Content-Type
도 필요함
</br>
Postman으로 확인하기
앱을 만들기 전에, 일단 Postman
으로 API를 잘 사용할 수 있는 지 확인해 보자.
POST
방식을 선택하고, 위에서 확인한 주소를 넣는다.
Params
에 Prediction-Key
와 Content-Type
을 적혀 있는 대로 추가해서 넣어 준다.
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()
로 보이게 할 수 있다.
버튼은 setTitle
과 setTitleColor
로 “Post Image”가 파란 글씨로 보이게 했다.
addTarget
에 누르면 수행할 함수를 연결한다.
텍스트뷰의 텍스트 색은 회색으로 했고, 폰트는 글자마다 같은 크기를 차지하는 Courier로 한다.
이미지뷰에 들어갈 이미지는 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)
}
}
}
따라서 버튼을 클릭했을 때 함수는 위와 같다.
각 width
와 height
는 모두 같은 한 변이 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
를 만든다.
거기에 httpMethod
를 POST
로 설정하고, Prediction-Key
와 Content-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문을 실행하면
위 사진처럼 나온다.
그럼 얘를 파싱해야 한다. Array<Dictionary<String, Any>>
로, 딕셔너리 어레이로 받는다.
포문을 돌면서, 확률이 제일 높은 애를 var c: Character
에 저장하고, 현재 위치인 ii
, jj
에 맞게 txt
배열에 저장해 주고, 텍스트뷰를 업데이트해 준다.
</br>
100*100 픽셀을 10픽셀씩 나눠서 보내 보자
결과
오잉? 왠진 모르겠는데 돌아가 있네 이미지 크롭하고 저장하고 출력하는 데에서 어디 뒤집혀서 그런가
그래도 그건 돌려주면 되는 거고 일단 앱으로도 잘 돌아간다는 걸 확인할 수 있었다!!
회색 부분은 %
로, 검은 선 부분은 @
로, 좀 애매하게 잘려 간 부분은 조금 애매하긴 하고
</br>
+
Prediction
에 잔뜩 쌓여 있는 잘린 이미지들
솔직히 사람인 나도 잘 모르겠다
</br>
++
지금 포스트 올리고 다시 검토하면서 보니까, 이미지 크롭하는 이중 포문에서 x에 i, y에 j를 넣었는데 CGRect는 x가 가로고 y가 세로라 뒤집힌 것 같다
</br>
적은 데이터로 짧게 학습해도 어느 정도는 구색은 갖추는 모습을 볼 수 있었다
로컬에 저장도 되는 걸로 안다. 로컬 프로그램으로 만들거나, 아니면 다른 사이트들처럼 웹으로 변환도 한 번 해 보거나 해볼까 그러려면 데이터를 잘 더 추가해야 할 거다
오늘은 여기로 마무리 해 보자!!
</br>
댓글남기기