Slide 1

Slide 1 text

"*Λ׆༻ͨ͠ϨγʔτಡΈऔΓػೳͷ ։ൃ͔ΒಘΒΕ࣮ͨફ஌  J04%$+BQBO !SPDLOBNF

Slide 2

Slide 2 text

㲔 Smart, Secure, Mobile Super powered Banking and Living.  --.Λ׆༻ͨ͠ػೳΛ։ൃͨ͜͠ͱ͕͋Δਓ🙋

Slide 3

Slide 3 text

X

Slide 4

Slide 4 text

 എܠ "*ϨγʔτಡΈऔΓ #·ͱΊ "*ࢧग़τʔΫ

Slide 5

Slide 5 text

 എܠ "*ϨγʔτಡΈऔΓ #·ͱΊ "*ࢧग़τʔΫ

Slide 6

Slide 6 text

ࠓ೔࿩͢͜ͱ

Slide 7

Slide 7 text

 ࠓ೔࿩͢͜ͱ  "*ϨγʔτಡΈऔΓػೳͷ֓ཁ  Ϩγʔτ0$3  'PVOEBUJPO.PEFMTͷՄೳੑ

Slide 8

Slide 8 text

 4NBSU#BOL *OD .PCJMF"QQMJDBUJPO%FWFMPQFS ϩΫωϜ !@SPDLOBNF !SPDLOBNF ࣗݾ঺հ

Slide 9

Slide 9 text

 "*ϨγʔτಡΈऔΓػೳͷ֓ཁ

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

 "*ϨγʔτಡΈऔΓػೳͷ֓ཁ όοΫΤϯυଆͰͷ࣮૷ʹ͍ͭͯৄࡉ͸ͪ͜Β IUUQTTQFBLFSEFDLDPNNP[OJPOGSPNHSPVOEFEUFDIDIPJDFTUPNBHJDBMVYBDBTFTUVEZPGBJ SFDFJQUTDBOOJOH !NP[OJPO

Slide 18

Slide 18 text

 Ϩγʔτ0$3

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOBWGPVOEBUJPOTFUUJOHVQBDBQUVSFTFTTJPO

Slide 25

Slide 25 text

let session = AVCaptureSession() func configureSession() { session.beginConfiguration() defer { session.commitConfiguration() } setupInput() setupOutput() } func setupInput() { … } func setupOutput() { … }

Slide 26

Slide 26 text

func configureSession() { … setupInput() setupOutput() } func setupInput() { let device = AVCaptureDevice.default( .builtInWideAngleCamera, for: .video, position: .back )! let deviceInput = try! AVCaptureDeviceInput(device: device) if session.canAddInput(deviceInput) { session.addInput(deviceInput) } } func setupOutput() { … }

Slide 27

Slide 27 text

func configureSession() { … setupInput() setupOutput() } func setupInput() { … } func setupOutput() { let videoDataOutput = AVCaptureVideoDataOutput() videoDataOutput.setSampleBufferDelegate( self, queue: sampleBufferQueue ) if session.canAddOutput(videoDataOutput) { session.addOutput(videoDataOutput) } videoDataOutput.connection(with: .video)?.isEnabled = true }

Slide 28

Slide 28 text

// AVCaptureVideoDataOutputSampleBufferDelegate method func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection ) { // TODO: ϏσΦϑϨʔϜͷϋϯυϦϯά }

Slide 29

Slide 29 text

 Ϩγʔτ0$34FUVQDBNFSB 0$3ͷਫ਼౓ΛߴΊΔͨΊʹ )JHI3FTPMVUJPO 'PDVT

Slide 30

Slide 30 text

 Ϩγʔτ0$34FUVQDBNFSB 0$3ͷਫ਼౓ΛߴΊΔͨΊʹ )JHI3FTPMVUJPO 'PDVT

Slide 31

Slide 31 text

func configureSession() { … if session.canSetSessionPreset(.hd4K3840x2160) { session.sessionPreset = .hd4K3840x2160 } … }

Slide 32

Slide 32 text

 Ϩγʔτ0$34FUVQDBNFSB 0$3ͷਫ਼౓ΛߴΊΔͨΊʹ )JHI3FTPMVUJPO 'PDVT

Slide 33

Slide 33 text

 Ϩγʔτ0$34FUVQDBNFSB Ϩγʔτ͸Χϝϥͱൺֱత ۙڑ཭ͰࡱӨ͞ΕΔɻ ͦͷલఏΛ "7$BQUVSF%FWJDF΁͋Β͔ ͡Ί఻͓͑ͯ͘ͱϑΥʔΧ ε͕߹͍΍͘͢ͳΔɻ ϑΥʔΧεΛ߹ΘͤΔ

Slide 34

Slide 34 text

func setupInput() { … if device.isAutoFocusRangeRestrictionSupported { device.autoFocusRangeRestriction = .near } … }

Slide 35

Slide 35 text

 Ϩγʔτ0$34FUVQDBNFSB ࠷খϑΥʔΧεڑ཭ͱ͸ɺ઱໌ͳϑΥʔ ΧεΛ߹ΘͤΔ͜ͱ͕Ͱ͖ΔɺϨϯζ͔ Βඃࣸମ·Ͱͷ࠷΋͍ۙڑ཭ͷ͜ͱɻ ඃࣸମ͕Χϝϥͷ࠷খϑΥʔΧεڑ཭Α Γ΋͍ۙ৔߹ɺϑΥʔΧεΛ߹ΘͤΒΕ ͣʹը૾͕΅΍͚ͯ͠·͏ɻ ࠷খϑΥʔΧεڑ཭Λߟྀ͢Δ

Slide 36

Slide 36 text

 Ϩγʔτ0$34FUVQDBNFSB ඞͣ࠷খϑΥʔΧεڑ཭Ҏ্཭͞ͳ͍ ͱϨγʔτ͕ΧϝϥϓϨϏϡʔʹऩ·Β ͳ͍Α͏ʹζʔϜΛద༻͢Δɻ 88%$ͷηογϣϯ8IBU`TOFXJO DBNFSBDBQUVSFʹͯ঺հ͞Ε͍ͯΔํ ๏ɻ ڑ཭͕։͘Α͏ʹζʔϜΛௐ੔

Slide 37

Slide 37 text

func setupInput() { … let minimumSubjectDistance = minimumSubjectDistance( fieldOfView: device.activeFormat.videoFieldOfView, minimumReceiptWidth: 60, // ૝ఆ͞ΕΔ࠷খͷϨγʔτ୹ล (mm) previewFillPercentage: 0.8 // ϓϨϏϡʔͷ෯ʹର͢Δඃࣸମͷׂ߹ ) } func minimumSubjectDistance( fieldOfView: Float, minimumReceiptWidth: Float, previewFillPercentage: Float ) -> Float { let radians = degreesToRadians(fieldOfView / 2.0) let filledSize = minimumReceiptWidth / previewFillPercentage return filledSize / tan(radians) }

Slide 38

Slide 38 text

func setupInput() { … let deviceMinimumFocusDistance = Float(device.minimumFocusDistance) if minimumSubjectDistance < deviceMinimumFocusDistance { let zoomFactor = deviceMinimumFocusDistance / minimumSubjectDistance device.videoZoomFactor = CGFloat(zoomFactor) } }

Slide 39

Slide 39 text

#FGPSF "GUFS

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

 Ϩγʔτ0$3%FUFDU3FDFJQU จࣈྻ͕هࡌ͞Εۣͨܗঢ়ͷΦϒ δΣΫτΛϨγʔτͱͯ͠ݕग़ɻ ͦͷ্ͰɺϢʔβʔͷखϒϨ͕ൃੜ ͠ͳ͍ϕετͳλΠϛϯάΛݕ஌͠ ͯ0$3Λ࣮ߦ͍ͨ͠ɻ Ϩγʔτݕ஌ϩδοΫ 

Slide 43

Slide 43 text

 Ϩγʔτ0$3%FUFDU3FDFJQU ݕग़ҐஔͱαΠζ͕ۙࣅ͠ଓ͚ͨ৔߹ʹϨγʔτΛݕ஌ͨ͠ͱΈͳͯ͠ 0$3΁Ҡߦ͢Δ͜ͱʹɻ Ϩγʔτݕ஌ϩδοΫ 

Slide 44

Slide 44 text

IUUQTTQFBLFSEFDLDPNOBLBNVVVJPTEDKBQBO TMJEF J04%$ʰγʔϜϨεͳମݧΛ࣮ݱ͢Δຊਓ֬ೝϑϩʔͷߏஙʱ !OBLBNVVV

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

 Ϩγʔτ0$3%FUFDU3FDFJQU "QQMFͷίϯϐϡʔλʔϏδϣϯ ϑϨʔϜϫʔΫɻ ػցֶशΛ༻͍ͯը૾ɾө૾ͷղ ੳΛΞϓϦ্Ͱ࣮ߦͰ͖Δɻ 7JTJPOGSBNFXPSL IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOWJTJPO

Slide 48

Slide 48 text

 Ϩγʔτ0$3%FUFDU3FDFJQU ը૾͔ΒจࣈΛؚΉۣܗͷྖҬΛ ݕग़͢ΔϦΫΤετɻ ·ͣ͸͜ΕͰը૾͔ΒϨγʔτͬ Ά͍ۣܗΛݕग़͢Δɻ %FUFDU%PDVNFOU4FHNFOUBUJPO3FRVFTU IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOWJTJPOEFUFDUEPDVNFOUTFHNFOUBUJPOSFRVFTU

Slide 49

Slide 49 text

 Ϩγʔτ0$3%FUFDU3FDFJQU ͋Β͔͡Ίಛఆ͞ΕۣͨܗΦϒ δΣΫτͷಈ͖Λɺෳ਺ͷը૾΍ ϏσΦϑϨʔϜʹΘͨͬͯ௥੻͢ Δը૾ղੳϦΫΤετɻ 5SBDL3FDUBOHMF3FRVFTU IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOWJTJPOUSBDLSFDUBOHMFSFRVFTU

Slide 50

Slide 50 text

actor ReceiptDetector { func processVideoFrame(sampleBuffer: CMSampleBuffer) async { await startToTrackReceiptRect(sampleBuffer: sampleBuffer) } private func startToTrackReceiptRect(sampleBuffer: CMSampleBuffer) async { do { let request = DetectDocumentSegmentationRequest() let detectedDocumentObservation = try await request.perform( on: sampleBuffer, orientation: .right ) } catch { … } } }

Slide 51

Slide 51 text

actor ReceiptDetector { private var trackRectangleRequest: TrackRectangleRequest? … private func startToTrackReceiptRect(sampleBuffer: CMSampleBuffer) async { … guard let detectedDocumentObservation, detectedDocumentObservation.confidence > 0.5 else { return } let trackingRequest = TrackRectangleRequest( detectedRectangle: detectedDocumentObservation, .revision1 ) trackingRequest.trackingLevel = .accurate self.trackRectangleRequest = trackingRequest }

Slide 52

Slide 52 text

actor ReceiptDetector { private var trackRectangleRequest: TrackRectangleRequest? func processVideoFrame(sampleBuffer: CMSampleBuffer) async { guard let trackRectangleRequest else { await startToTrackReceiptRect(sampleBuffer: sampleBuffer) return } await trackReceiptRect( trackRectangleRequest: trackRectangleRequest, sampleBuffer: sampleBuffer ) } private func trackReceiptRect(…) async { … } }

Slide 53

Slide 53 text

actor ReceiptDetector { … private func trackReceiptRect( trackRectangleRequest: TrackRectangleRequest, sampleBuffer: CMSampleBuffer ) async { do { let rectangleObservation = try await trackRectangleRequest.perform( on: sampleBuffer, orientation: .right ) // ✅ ݕग़ҐஔͱαΠζ͕ۙࣅ͠ଓ͚͍ͯΔ͔Ͳ͏͔Λ൑ఆ͢Δ } catch { … } } }

Slide 54

Slide 54 text

 Ϩγʔτ0$3%FUFDU3FDFJQU ຖϑϨʔϜը૾ॲཧΛ࣮ߦ͢Δ ͱෛՙ͕ߴ͍ɻ ը૾ॲཧΛ࣮ߦதͷ৔߹͸ૣظ Ϧλʔϯ͠ɺಉ࣌ʹෳ਺ͷը૾ ॲཧ͕૸Βͳ͍Α͏ʹ͢Δɻ ύϑΥʔϚϯεվળ5JQT

Slide 55

Slide 55 text

actor ReceiptDetector { private var isDetectingReceipt = false … func processVideoFrame(sampleBuffer: CMSampleBuffer) async { if isDetectingReceipt { return } isDetectingReceipt = true defer { isDetectingReceipt = false } … } … }

Slide 56

Slide 56 text

 Ϩγʔτ0$3%FUFDU3FDFJQU Ϩγʔτݕ஌ʹ͓͍ͯ͸ը૾͸ ͦ͜·Ͱߴղ૾౓Ͱ͋Δඞཁ͸ ͳ͍ɻ খ͘͞ϦαΠζͨ͠ը૾ʹର͠ ۣͯܗݕग़ͱ௥੻ͷϦΫΤετ Λ࣮ߦ͢Δɻ ύϑΥʔϚϯεվળ5JQT

Slide 57

Slide 57 text

guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let originalImage = CIImage(cvImageBuffer: pixelBuffer) let originalSize = originalImage.extent.size let originalShortSide = min(originalSize.width, originalSize.height) let targetShortSide: CGFloat = 512 let resizeScale = min(1.0, targetShortSide / originalShortSide) let resizedImage = originalImage.transformed( by: CGAffineTransform(scaleX: resizeScale, y: resizeScale) ) …

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

 Ϩγʔτ0$33FDPHOJ[F5FYU ը૾಺ͷςΩετΛೝࣝ͢ΔϦΫ Τετɻ 3FDPHOJ[F5FYU3FRVFTU IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOWJTJPOSFDPHOJ[FUFYUSFRVFTU

Slide 61

Slide 61 text

final class ReceiptTextRecognizer { func recognizeText(sampleBuffer: CMSampleBuffer) async throws -> [String] { var request = RecognizeTextRequest(.revision3) request.recognitionLanguages = [ Locale.Language(identifier: "ja-JP"), Locale.Language(identifier: "en-US") ] request.recognitionLevel = .accurate let result = try await request.perform( on: sampleBuffer, orientation: .right ) } }

Slide 62

Slide 62 text

 Ϩγʔτ0$3%FUFDU3FDFJQU ςΩετೝࣝ͸͔ͳΓॲཧ͕ॏ ͍ɻ Ͱ͖Δ͚ͩෛՙΛখ͘͢͞Δͨ ΊʹSFHJPO0G*OUFSFTUͰςΩε τೝࣝͷର৅ྖҬΛݶఆ͢Δɻ ύϑΥʔϚϯεվળ5JQT

Slide 63

Slide 63 text

func recognizeText( sampleBuffer: CMSampleBuffer, receiptRect: NormalizedRect ) async throws -> [String] { var request = RecognizeTextRequest(.revision3) let expandedRect = expandNormalizedRect(receiptRect, percent: 0.05) request.regionOfInterest = expandedRect … } private func expandNormalizedRect( _ rect: NormalizedRect, percent: CGFloat ) -> NormalizedRect { // ✅ ۣܗΛ෯ɾߴ͞ͷׂ߹ʢpercentʣ͚࢛ͩํʹ֦ு͢Δ }

Slide 64

Slide 64 text

 Ϩγʔτ0$33FDPHOJ[F5FYU ࣮ߦ݁Ռͱͯ͠ʮը૾಺ͰςΩε τͱ൑ఆ͞Ε֤ͨྖҬʯΛද͢ 3FDPHOJ[FE5FYU0CTFSWBUJPOͷ഑ ྻ͕ฦΔɻ ֤ྖҬʹ͸ը૾಺ʹ͓͚Δ࠲ඪ͕ ׂΓৼΒΕ͍ͯΔɻ 3FDPHOJ[F5FYU3FRVFTU3FTVMU

Slide 65

Slide 65 text

{ "string": "ίΧɾίʔϥ ϠΧϯϊϜΪνϟ", "bounding_box": { "x": 0.11, "y": 0.70, "width": 0.38, "height": 0.01 } }, { "string": "650ml", "bounding_box": { "x": 0.49, "y": 0.70, "width": 0.14, "height": 0.01 } }, …

Slide 66

Slide 66 text

{ "string": "ίΧɾίʔϥ ϠΧϯϊϜΪνϟ", "bounding_box": { "x": 0.11, "y": 0.70, "width": 0.38, "height": 0.01 } }, { "string": "650ml", "bounding_box": { "x": 0.49, "y": 0.70, "width": 0.14, "height": 0.01 } }, …

Slide 67

Slide 67 text

 Ϩγʔτ0$33FDPHOJ[F5FYU Ϩγʔτͷ্͔ΒԼʹฒͿςΩετ ͱಉ͡ॱংͰߦͣͭվߦ۠੾ΓͰ ੔ܗ͢Δɻ গͳ͍τʔΫϯ਺Ͱ֤จࣈྻͷ࠲ඪ ৘ใΛ఻͑Δ͜ͱ͕Ͱ͖Δɻ --.͕ղऍ͠΍͍͢ܗʹલॲཧ

Slide 68

Slide 68 text

[ "ηϒϯ-ΠϨϒϯ", "඼઒౦ޒ൓ా1ஸ໨ళ", "౦ژ౎඼઒۠౦ޒ൓ా1-12-2", "ి࿩ɿ03-3440-5055 Ϩδˌ1", "ࣄۀऀొ࿥൪߸5010701018426", "2024೥09݄13೔ʢۚʣ09:22 ੹210", "ྖऩॻ", "ίΧɾίʔϥ ϠΧϯϊϜΪνϠ 650ml ˎ138", "஋Ҿֹ -20”, "ʢ঎඼୅ۚ ¥138ʣ", … ]

Slide 69

Slide 69 text

 Ϩγʔτ0$33FDPHOJ[F5FYU ༨ஊ88%$Ͱൃද͞Εͨ3FDPHOJ[F%PDVNFOUT3FRVFTU IUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZXXED

Slide 70

Slide 70 text

PostalAddress( fullAddress: "౦ژ౎඼઒۠౦ޒ൓ా 1 - 12 - 2", street: "౦ޒ൓ా1 - 12 - 2", city: "඼઒۠", state: "౦ژ౎" )

Slide 71

Slide 71 text

PhoneNumber( phoneNumber: "03-3440-5055", label: "Work" )

Slide 72

Slide 72 text

CalendarEvent( allDay: false, startDate: "2024-09-13 09:22:00 +0000", startTimeZone: nil, endDate: nil, endTimeZone: nil )

Slide 73

Slide 73 text

MoneyAmount( currency: jpy, amount: 138 ) MoneyAmount( currency: jpy, amount: 127 )

Slide 74

Slide 74 text

MoneyAmount( currency: jpy, amount: 138 ) MoneyAmount( currency: jpy, amount: 127 )

Slide 75

Slide 75 text

 Ϩγʔτ0$3%FUFDU3FDFJQU w ॅॴ΍ి࿩൪߸ʹ͍ͭͯ͸͔ͳΓਫ਼౓ߴ͘நग़ͯ͘͠Εͦ͏ w ֹۚͷநग़ʹ͍ͭͯ͸Ϝϥ͕͋ΔͨΊ׬શʹ৴པ͢Δ͜ͱ͸೉ͦ͠ ͏ 3FDPHOJ[F%PDVNFOUT3FRVFTU΁ͷධՁ

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

 'PVOEBUJPO.PEFMTͷՄೳੑ

Slide 79

Slide 79 text

 'PVOEBUJPO.PEFMTͷՄೳੑ "QQMF*OUFMMJHFODFͷݴޠϞσ ϧΛ4XJGU͔Βܕ҆શʹѻ͑Δ ϑϨʔϜϫʔΫͰɺཁ໿ɾந ग़ɾϦϥΠτ౳ΛΦϯσόΠε ͔ͭϓϥΠόγʔ༏ઌͰ࣮ߦ͠ ·͢ɻ 'PVOEBUJPO.PEFMTͱ͸ IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPO'PVOEBUJPO.PEFMT

Slide 80

Slide 80 text

'PVOEBUJPO.PEFMTͰ Ϩγʔτ৘ใͷߏ଄Խ͸Ͱ͖Δͷ͔ʜʁ

Slide 81

Slide 81 text

@Generable struct ReceiptInferenceResponse { @Guide(description: "Merchant/store name.") let shopName: String? @Guide(description: "Purchase (transaction) date normalized to ISO 8601 `YYYY-MM-DD`.") let date: String? @Guide(description: "Grand total amount paid in JPY (tax- inclusive), as a non-negative integer.") let amount: Int? @Guide(description: "Category for this receipt.") let category: Category? }

Slide 82

Slide 82 text

final class ReceiptStructureInferrer { private let session: LanguageModelSession init() { self.session = LanguageModelSession( instructions: Instructions { """ … """ } ) } }

Slide 83

Slide 83 text

Instructions { """ You are an information extractor for retail receipts. Carefully read through the input text in Japanese, scanned from a receipt. Line breaks correspond to each line of the receipt from top to bottom. Extract receipt information and return it in the provided schema format Each category definition for a receipt is following: - groceries: foodstuff, groceries - diningOut: payments related to restaurants, bars, and other dining expenses - householdSupplies: daily necessities, excluding food items … """ }

Slide 84

Slide 84 text

final class ReceiptStructureInferrer { … func infer(receiptTextLines: [String]) async throws -> ReceiptInferenceResponse { let prompt = Prompt { … } let response = try await session.respond( to: prompt, generating: ReceiptInferenceResponse.self, options: GenerationOptions(temperature: 0.1) ) return response.content } }

Slide 85

Slide 85 text

Prompt { """ ## Task Extract fields from the following OCR lines of a Japanese retail receipt. ## Input \(receiptTextLines.joined(separator: "\n")) ## Requirements - The output format must be valid. - Ensure that the output is complete and not truncated. - If any information is missing or unclear, use "nil" for that field. """ }

Slide 86

Slide 86 text

ಈ͔ͯ͠ΈΔ📸

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

 'PVOEBUJPO.PEFMTͷՄೳੑ w ళ໊ɾ೔෇ͷநग़͸݁ߏ͏·͍͘͘ w ֹۚͷநग़ɺΧςΰϦͷਪ࿦पΓͰগ͠՝୊͋Γ w ͜Ε͕ແྉ͔ͭMPDBM׬݁Ͱମݧͱͯ͠ఏڙͰ͖Δͷ͸͍͢͝ 'PVOEBUJPO.PEFMTͷධՁ

Slide 91

Slide 91 text

 ·ͱΊ

Slide 92

Slide 92 text

 ·ͱΊ w "*ϨγʔτಡΈऔΓػೳ📸 w --.ͷlߏ଄Խzͱ͍͏ڧΈΛ׆͔ͯ҆͠Ձʹߴ͍ਫ਼౓Λ࣮ݱͨ͠ w (BSCBHFJO HBSCBHFPVU🔥 w ΠϯϓοτΛ͍͔ʹ៉ྷʹ͢Δ͔͕--.ͷਪ࿦ͷਫ਼౓ʹ௚݁͢Δ w 'PVOEBUJPO.PEFMT🧠 w -PDBM--.ͷϙςϯγϟϧΛײͤͨ͡͞

Slide 93

Slide 93 text

͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ