実現したいこと
Swiftを用いたiPhone向け画像二値化アプリの実装
前提
プログラミング初心者のため、コード、文章共に稚拙な部分があると思いますがお力添えをいただければ幸いです。
Swiftを用いたiPhone向け画像二値化アプリを作成しています。
特定の画像の二値化の処理を行った際、後述のようなエラーメッセージが発生しました。
発生している問題・エラーメッセージ
cat.jpgをフォトライブラリーから読み込んだ際、次のエラーメッセージが発生します。
Thread 1: Fatal error: Index out of range
該当のソースコード
Swift
1// ContentView.swift 2 3import SwiftUI 4import UIKit 5import PhotosUI 6 7extension UIImage { 8 func createBinarizedImage(r:[CGFloat], g: [CGFloat], b:[CGFloat], a:[CGFloat], threshold:CGFloat) -> UIImage{ 9 UIGraphicsBeginImageContextWithOptions(size, false, 0) 10 let wid:Int = Int(size.width) 11 let hei:Int = Int(size.height) 12 for w in 0..<wid { 13 for h in 0..<hei { 14 let index = (w * wid) + h 15 var color = 0.2126 * r[index] + 0.7152 * g[index] + 0.0722 * b[index] 16 if color > threshold / 100 { 17 color = 255 18 } else { 19 color = 0 20 } 21 UIColor(red: color, green: color, blue: color, alpha: a[index]).setFill() 22 let drawRect = CGRect(x: w, y: h, width: 1, height: 1) 23 UIRectFill(drawRect) 24 draw(in: drawRect, blendMode: .destinationIn, alpha: 1) 25 } 26 } 27 let binarizeImage = UIGraphicsGetImageFromCurrentImageContext()! 28 UIGraphicsEndImageContext() 29 return binarizeImage 30 } 31 32 // resize image 33 func resize () -> UIImage? { 34 let rate = 350.0 / self.size.width 35 let rect = CGRect(x: 0, 36 y: 0, 37 width: self.size.width * rate, 38 height: self.size.height * rate) 39 40 UIGraphicsBeginImageContext (rect.size) 41 self.draw(in: rect) 42 let image = UIGraphicsGetImageFromCurrentImageContext() 43 UIGraphicsEndImageContext () 44 return image 45 } 46} 47 48class ViewController: UIViewController { 49 @IBOutlet weak var image: UIImageView! 50 51 var r:[CGFloat] = [] 52 var g:[CGFloat] = [] 53 var b:[CGFloat] = [] 54 var a:[CGFloat] = [] 55 56 var threshold:CGFloat = 0.0 57 58 override func viewDidLoad() { 59 super.viewDidLoad() 60 let img = UIImage(named: "Lena")! 61 if let pixelBuffer = PixelBuffer(uiImage: img) { 62 for x in 0..<pixelBuffer.width { 63 for y in 0..<pixelBuffer.height { 64 r.append(pixelBuffer.getRed(x: x, y: y)) 65 g.append(pixelBuffer.getBlue(x: x, y: y)) 66 b.append(pixelBuffer.getGreen(x: x, y: y)) 67 a.append(pixelBuffer.getAlpha(x: x, y: y)) 68 } 69 } 70 } else { 71 print("image not format") 72 } 73 image.image = img.createBinarizedImage(r: r, g: g, b: b, a: a, threshold: threshold) 74 } 75} 76 77class PixelBuffer { 78 private var pixelData: Data 79 var width: Int 80 var height: Int 81 private var bytesPerRow: Int 82 private let bytesPerPixel = 4 //1ピクセルが4バイトのデータしか扱わない 83 84 init?(uiImage: UIImage) { 85 guard let cgImage = uiImage.cgImage, 86 //R,G,B,A各8Bit 87 cgImage.bitsPerComponent == 8, 88 //1 pixelが32bit 89 cgImage.bitsPerPixel == bytesPerPixel * 8 else { 90 return nil 91 } 92 pixelData = cgImage.dataProvider!.data! as Data 93 width = cgImage.width 94 height = cgImage.height 95 bytesPerRow = cgImage.bytesPerRow 96 } 97 98 func getRed(x: Int, y: Int) -> CGFloat { 99 let pixelInfo = bytesPerRow * y + x * bytesPerPixel 100 let r = CGFloat(pixelData[pixelInfo]) / CGFloat(255.0) 101 102 return r 103 } 104 func getGreen(x: Int, y: Int) -> CGFloat { 105 let pixelInfo = bytesPerRow * y + x * bytesPerPixel 106 let green = CGFloat(pixelData[pixelInfo+1]) / CGFloat(255.0) 107 108 return green 109 } 110 func getBlue(x: Int, y: Int) -> CGFloat { 111 let pixelInfo = bytesPerRow * y + x * bytesPerPixel 112 let blue = CGFloat(pixelData[pixelInfo+2]) / CGFloat(255.0) 113 114 return blue 115 } 116 func getAlpha(x: Int, y: Int) -> CGFloat { 117 let pixelInfo = bytesPerRow * y + x * bytesPerPixel 118 let alpha = CGFloat(pixelData[pixelInfo+3]) / CGFloat(255.0) 119 return alpha 120 } 121} 122 123 124 125struct ContentView: View { 126 127 @State var isPhotolibrary = false 128 129 @State var isShowAction = false 130 @State var isShowSheet = false 131 132 @State var bin = 50.0 133 134 @State var isEditing = false 135 136 @State var captureImage: UIImage? = nil 137 @State var img = UIImage(named: "Lena") 138 139 @State var img_bin :UIImage? 140 141 142 init() { 143 if let pixelBuffer = PixelBuffer(uiImage: img!) { 144 var r = [CGFloat]() 145 var g = [CGFloat]() 146 var b = [CGFloat]() 147 var a = [CGFloat]() 148 for x in 0..<pixelBuffer.width { 149 for y in 0..<pixelBuffer.height { 150 r.append(pixelBuffer.getRed(x: x, y: y)) 151 g.append(pixelBuffer.getGreen(x: x, y: y)) 152 b.append(pixelBuffer.getBlue(x: x, y: y)) 153 a.append(pixelBuffer.getAlpha(x: x, y: y)) 154 } 155 } 156 _img_bin = State(initialValue: img!.createBinarizedImage(r: r, g: g, b: b, a: a, threshold:bin)) 157 } else { 158 print("image not format") 159 } 160 } 161 162 // --- img_binを設定し直しすためのメソッドです。 163 func setimgbin() { 164 //img = img?.resize() 165 if let pixelBuffer = PixelBuffer(uiImage: img!) { 166 var r = [CGFloat]() 167 var g = [CGFloat]() 168 var b = [CGFloat]() 169 var a = [CGFloat]() 170 for x in 0..<pixelBuffer.width { 171 for y in 0..<pixelBuffer.height { 172 r.append(pixelBuffer.getRed(x: x, y: y)) 173 g.append(pixelBuffer.getGreen(x: x, y: y)) 174 b.append(pixelBuffer.getBlue(x: x, y: y)) 175 a.append(pixelBuffer.getAlpha(x: x, y: y)) 176 } 177 } 178 // --- createBinarizedImageが遅いためスライダーが滑らかになりません 179 self.img_bin = img!.createBinarizedImage(r: r, g: g, b: b, a: a, threshold:bin) 180 } else { 181 print("image not format") 182 } 183 } 184 185 var body: some View { 186 187 VStack{ 188 Image(uiImage:img!) 189 if let unwrap_img_bin = img_bin { 190 Image(uiImage:unwrap_img_bin) 191 } 192 else{ 193 Text("アンラップ失敗") 194 } 195 Slider( 196 value: $bin, 197 in: 0...100, 198 step: 1.0, 199 onEditingChanged: { 200 editing in 201 isEditing = editing 202 setimgbin() 203 } 204 ) 205 206 Text("\(bin, specifier: "%.0f")") 207 .foregroundColor(isEditing ? .red : .blue) 208 209 Button(action: { 210 211 //isPhotolibrary = true 212 isShowAction = true 213 214 }){ 215 Text("Act") 216 .frame(maxWidth: .infinity) 217 .frame(height: 50) 218 .multilineTextAlignment(.center) 219 .background(Color.blue) 220 .foregroundColor(Color.white) 221 } 222 .padding() 223 .sheet(isPresented: $isShowSheet) { 224 225 if isPhotolibrary { 226 PHPickerView(isShowSheet: $isShowSheet, captureImage: $img)//写真のサイズが大きすぎる 227 }else{ 228 ImagePickerView(isShowSheet: $isShowSheet, captureImage: $img) 229 } 230 231 } 232 }.actionSheet(isPresented: $isShowAction){ 233 ActionSheet(title: Text("確認"), 234 message: Text("選択してください"), 235 buttons: [ 236 .default(Text("カメラ"), action: { 237 isPhotolibrary = false 238 239 if UIImagePickerController.isSourceTypeAvailable(.camera){ 240 241 print("カメラは利用できます") 242 isShowSheet = true 243 244 }else { 245 print("カメラは利用できません") 246 } 247 248 }), 249 .default(Text("フォトライブラリー"), action: { 250 isPhotolibrary = true 251 isShowSheet = true 252 print("フォトライブラリー") 253 }), 254 .cancel(), 255 ]) 256 } 257 } 258 259 260 struct ContentView_Previews: PreviewProvider { 261 static var previews: some View { 262 ContentView() 263 } 264 } 265} 266 267
試したこと
・Lenaの二値化は問題ありませんでした。
・フォトライブラリーからEarth.jpgを選択した場合、エラーメッセージは表示されず、二値化も問題なく行うことができました。
補足情報(FW/ツールのバージョンなど)
Xcode(Version 14.3)
MacBook Pro 13 (macOS 13.3.1)
iPhone11 (iOS 16.4.1)
追記:文字数の関係で本文に記載できなかったため、ここにソースコードの続きを記載します
// ImagePickerView.swift
import SwiftUI
struct ImagePickerView: UIViewControllerRepresentable {
@Binding var isShowSheet: Bool
@Binding var captureImage: UIImage?
class Coordinator: NSObject,
UINavigationControllerDelegate,
UIImagePickerControllerDelegate{
let parent: ImagePickerView
init(_ parent: ImagePickerView){
self.parent = parent
}
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info:
[UIImagePickerController.InfoKey : Any]) {
if let originalImage =
info[UIImagePickerController.InfoKey.originalImage]
as? UIImage {
parent.captureImage = originalImage
}
parent.isShowSheet = false
}
func imagePickerControllerDidCancel(
_ picker: UIImagePickerController) {
parent.isShowSheet = false
}
}
func makeCoordinator() -> Coordinator{
Coordinator(self)
}
func makeUIViewController(
context: UIViewControllerRepresentableContext<ImagePickerView>)->
UIImagePickerController {
let myImagePickerController = UIImagePickerController()
myImagePickerController.sourceType = .camera
myImagePickerController.delegate = context.coordinator
return myImagePickerController
}
func updateUIViewController(
_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePickerView>)
{
}
}
let index = (w * wid) + h
じゃなくて
let index = (h * wid) + w
なような…。
ご回答ありがとうございます!!!
早速14行目を
let index = (h * wid) + w
変更してみましたが、二値化処理後の画像が90°左に回転(Lenaで言うと、頭が左になっている)してしまいました。
上記の問題も解決されていないので、違うところに原因がありそうです、、、
じゃあ let index = (w * hei) + h かな…。
ありがとうございます!!!無事に作動しました!!!