Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
OCRを使ってゲームのアイテムをデータ化する
Search
Kishikawa Katsumi
May 22, 2026
Programming
170
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
OCRを使ってゲームのアイテムをデータ化する
プロトタイプを製品にする技術
OCRを使ってゲームのアイテムをデータ化する
Kishikawa Katsumi
May 22, 2026
More Decks by Kishikawa Katsumi
See All by Kishikawa Katsumi
Running Swift without an OS
kishikawakatsumi
0
990
浮動小数の比較について
kishikawakatsumi
0
610
Automatic Grammar Agreementと Markdown Extended Attributes について
kishikawakatsumi
0
270
愛される翻訳の秘訣
kishikawakatsumi
3
490
Private APIの呼び出し方
kishikawakatsumi
3
1k
iOSでSVG画像を扱う
kishikawakatsumi
0
260
Build your own WebP codec in Swift
kishikawakatsumi
2
2.7k
iOSDC 2024 SMBファイル共有をSwiftで実装する
kishikawakatsumi
1
340
Enhancing Applications with Accessibility API
kishikawakatsumi
3
6.3k
Other Decks in Programming
See All in Programming
Oxlintはいいぞ(続)
yug1224
1
500
[DroidKaigi 26] How We Moved from Android Studio to Slack
thisaay
0
120
GKE アップグレード前に知っておきたい Blue/Green と PDB の関係
stkk
0
120
T3DD26: From RAGs to Riches
martinhelmich
0
130
夏だ!祭りだ!祭りとはドメインモデリングでは?
ryugen04
0
490
【QA Test Talk Vol.8】AI-DLC による Whole Team Approach の加速
pkshadeck
PRO
0
270
レビュー履歴をAIに食わせて、 Compose移行を加速するs
shihochan
0
260
いまどきの Codex で開発する visionOS アプリの開発スタイルについて
karad
0
170
LLMは4年分のCompose移行を再現できるのか?実プロダクト279件のXMLで探る自動化の境界線
makun
0
240
KotlinConf Extended South Korea 2026 Keynote
l2hyunwoo
0
130
Webエンジニアなのにブラウザの仕組みがわからないので、Pythonで自作してみた
tatsuki12
4
1.6k
片田舎のおっさん、 Swift Buildのダイアモンド問題解決の不具合修正PRを出すが、解決方法がキャッシュをしないようにすることであり、ビルド時間が伸びると言われてマージされないので高速化もする/swiftbuild
yimajo
0
330
Featured
See All Featured
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
460
For a Future-Friendly Web
brad_frost
183
10k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
510
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
480
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Why You Should Never Use an ORM
jnunemaker
PRO
61
10k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
880
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
390
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
580
Crafting Experiences
bethany
1
270
Transcript
ϓϩτλΠϓΛʹ͢Δٕज़ LJTIJLBXBLBUTVNJ !LJTIJLBXBLBUTVNJ!IBDIZEFSNJP LJTIJLBXBLBUTVNJ 0$3ΛͬͯήʔϜͷΞΠςϜΛσʔλԽ͢Δ
None
ϓϩτλΠϓΛʹ͢Δٕज़ r ݸͷΞΠςϜΛਖ਼֬ʹσʔλԽ͢Δ r ̍ͭʹ͖ͭඵҎʹಡΈऔΕΔ r ήʔϜͷݴޠ͕ຊޠͱӳޠͷͲͪΒͰಡΈऔΕΔ
ϓϩτλΠϓΛʹ͢Δٕज़ r ݸͷΞΠςϜΛਖ਼֬ʹσʔλԽ͢Δ r ̍ͭʹ͖ͭඵҎʹಡΈऔΕΔ r ήʔϜͷݴޠ͕ຊޠͱӳޠͷͲͪΒͰಡΈऔΕΔ ਫ਼
ϓϩτλΠϓΛʹ͢Δٕज़ r ݸͷΞΠςϜΛਖ਼֬ʹσʔλԽ͢Δ r ̍ͭʹ͖ͭඵҎʹಡΈऔΕΔ r ήʔϜͷݴޠ͕ຊޠͱӳޠͷͲͪΒͰಡΈऔΕΔ ਫ਼
ϓϩτλΠϓΛʹ͢Δٕज़ r ݸͷΞΠςϜΛਖ਼֬ʹσʔλԽ͢Δ r ̍ͭʹ͖ͭඵҎʹಡΈऔΕΔ r ήʔϜͷݴޠ͕ຊޠͱӳޠͷͲͪΒͰಡΈऔΕΔ ਫ਼ ॊೈੑ
4BNQMF$PEF HJUIVCDPNLJTIJLBXBLBUTVNJ$BNFSB0$3
None
None
େ͖͞ ৭ छྨ ޮՌςΩετ
4UFQ3BX0$3 actor OCRRunner { private var busy = false func
process(_ cgImage: CGImage) async -> [RecognizedTextObservation]? { guard !busy else { return nil } busy = true defer { busy = false } var request = RecognizeTextRequest() request.recognitionLanguages = [ Locale.Language(identifier: "ja-JP"), Locale.Language(identifier: "en-US"), ] request.recognitionLevel = .accurate request.usesLanguageCorrection = false return try? await request.perform(on: cgImage) } }
None
4UFQ4UBCJMJUZ'JMUFS
let windowSize: Int = 5 let minHits: Int = 3
func updateStability(with results: [RecognizedTextObservation]) { let textsThisFrame = Set( results.compactMap { (observation) -> String? in let raw = observation.topCandidates(1).first?.string ?? "" let t = raw.trimmingCharacters(in: .whitespacesAndNewlines) return t.isEmpty ? nil : t } ) recentTextSets.append(textsThisFrame) if recentTextSets.count > windowSize { recentTextSets.removeFirst(recentTextSets.count - windowSize) } var counts: [String: Int] = [:] for set in recentTextSets { for t in set { counts[t, default: 0] += 1 } } let stable = counts.filter { $0.value >= minHits } stableTexts = Set(stable.keys) stableLines = stable .map { StableLine(text: $0.key, hits: $0.value) } .sorted { $0.hits == $1.hits ? $0.text < $1.text : $0.hits > $1.hits } } ϑϨʔϜͰճҎ্ग़ݱͨ͠ ςΩετΛ࠾༻͢Δɻ
None
Ԍ߈ܸྗ্ঢ Ԍ߈ܸྗ্ঢ ཕ߈ܸྗ্ঢ Ԍ߈ܸྗ্ঢ ཕ߈ܸྗ্ঢ ग़ܸ࣌ͷثʹ ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ
ཕ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ ཕ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ ཕ߈ܸྗΛՃ ϦϯάόοϑΝʹΑΔ҆ఆੑͷ্ /ϑϨʔϜத.ճҎ্ग़ͨςΩετΛ࠾༻͢Δ
Ԍ߈ܸྗ্ঢ Ԍ߈ܸྗ্ঢ ཕ߈ܸྗ্ঢ Ԍ߈ܸྗ্ঢ ཕ߈ܸྗ্ঢ ग़ܸ࣌ͷثʹ ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ
ཕ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ ཕ߈ܸྗΛՃ ग़ܸ࣌ͷثʹ ཕ߈ܸྗΛՃ ϦϯάόοϑΝʹΑΔ҆ఆੑͷ্ /ϑϨʔϜத.ճҎ্ग़ͨςΩετΛ࠾༻͢Δ
4UFQ30* 3FHJPOPG*OUFSFTU
ΨΠυͷൣғ͚ͩಡΈऔΔ ؔͳ͍ςΩετΛಡ·ͳ͍ɾ্
static let roiOnScreen = CGRect(x: 0.08, y: 0.30, width: 0.84,
height: 0.32) static let visionROI: NormalizedRect = { let r = roiOnScreen return NormalizedRect(x: r.minX, y: 1 - r.maxY, width: r.width, height: r.height) }() func process( _ cgImage: CGImage, roi: NormalizedRect ) async -> [RecognizedTextObservation]? { ... request.regionOfInterest = roi ... } 6*ͷ࠲ඪͷ7JTJPOGSBNFXPSLͷ ࠲ඪʹมͯ͠ηοτ ΨΠυͷൣғ͚ͩಡΈऔΔ ؔͳ͍ςΩετΛಡ·ͳ͍ɾ্
None
4UFQ.BTUFS.BUDIJOH
Ϛελʔσʔλͱর߹
func bestMatch(for input: String) -> (master: Master, distance: Int)? {
let n = normalize(input) var best: (Master, Int)? for (key, master) in normalizedKeys { if abs(key.count - n.count) > 5 { continue } let d = levenshtein(n, key) if best == nil || d < best!.1 { best = (master, d) } } return best } // डཧ: ڑ ≤ max(1, |master| × 0.3) ≒ score ≥ 0.70 let threshold = max(1, Int(Double(master.textJa.count) * 0.3)) guard match.distance <= threshold else { return nil } ฤूڑ -FWFOTIUFJO%JTUBODF ͰҰகΛఆ Ϛελʔσʔλͱর߹
None
ΞϧΰϦζϜ )BNNJOHڑ ܭࢉྔͱΈ ࠷ 903 QPQDPVOU ɻಉ͡͞ͷจࣈྻͰʮҟͳΔҐஔͷʯΛ͑Δ ࠾༻͠ͳ͔ͬͨཧ༝ 0$3ͷจࣈGSBNF͝ͱʹ༳ΕΔͨΊద༻ෆՄɻจࣈͰ͕͞ҧ͏ͱ͑ͳ͍ ΞϧΰϦζϜ
OHSBN+BDDBSEྨࣅ ܭࢉྔͱΈ ͍ ू߹ԋࢉ ɻจࣈ/HSBNू߹Λ࡞Γc"ˬ#cc"˫#cΛܭࢉ ࠾༻͠ͳ͔ͬͨཧ༝ จࣈॱংΛࣺͯΔͨΊʮ߈ܸྗ্ঢʯͱʮ্ঢ߈ܸྗʯΛ۠ผͰ͖ͳ͍ ΞϧΰϦζϜ %BNFSBV-FWFOTIUFJO ܭࢉྔͱΈ -FWFOTIUFJOͱ΄΅ಉ Θ͔ͣʹ͍ ɻ-FWFOTIUFJO ྡจࣈͷೖΕସ͑ΛίετͰڐ༰ ࠾༻͠ͳ͔ͬͨཧ༝ 0$3ͰUSBOTQPTJUJPOΑΓ७ਮͳஔޡΓ͕େͰɺԸܙ͕ബ͍ ΞϧΰϦζϜ +BSP8JOLMFS ܭࢉྔͱΈ -FWFOTIUFJOΑΓఆ͕খ͍͞ɻҰகจࣈ USBOTQPTJUJPO ڞ௨QSF fi YՃͰྨࣅΛग़͢ ࠾༻͠ͳ͔ͬͨཧ༝ ͍ਓ໊ɾॅॴ͚ʹ࠷దԽ͞ΕͨؔͰɺʙจࣈͷFGGFDUจͰ-FWFOTIUFJOͱͷ͕ࠩग़ʹ͍͘ ͦͷଞͷর߹ΞϧΰϦζϜ
ͦͷଞͷর߹ΞϧΰϦζϜ ΞϧΰϦζϜ 4ZN4QFMM ܭࢉྔͱΈ ࣄલܭࢉͰ࣮࣭0 MPPLVQɻNBTUFSΛʮFEJU≤Lͷશมܗʯʹల։ͨࣙ͠ॻΛQSFCVJME͠ɺೖྗల։ͯ͠IBTIিಥΛݕग़ ࠾༻͠ͳ͔ͬͨཧ༝ ڑ≤·Ͱ͔͠Ҿ͚ͣ͞มಈʹऑ͍ɻࣙॻల։Ͱ0 -
ͷϝϞϦு͕͋Γɺ݅نͰԸܙ͕͍͠ ΞϧΰϦζϜ #,USFF ܭࢉྔͱΈ ฏۉ0 MPH/ ఔͷۙ୳ࡧɻจࣈྻۭؒʹNFUSJDUSFFΛߏங͠ɺڑEҎͷͷΛͰߜΔ ࠾༻͠ͳ͔ͬͨཧ༝ ෦Ͱ݁ہ-FWFOTIUFJOΛݺͿɻ݅نͰΠϯσοΫεߏஙίετ͕ԸܙΛ্ճΔ ΞϧΰϦζϜ 4PVOEFY.FUBQIPOF ԻӆIBTI ܭࢉྔͱΈ ͍ ఆ࣌ؒ ɻൃԻྨࣅੑͰಉΫϥεΛ࡞ΓɺϋογϡҰகͰൺֱ ࠾༻͠ͳ͔ͬͨཧ༝ ӳޠԻӆ͚ͷࢉ๏Ͱ͋Γɺຊޠ$+,ʹద༻Ͱ͖ͳ͍ ΞϧΰϦζϜ จ຺ϞσϧຒΊࠐΈڑ #&35 ܭࢉྔͱΈ େ෯ʹ͍ ेNTΫΤϦ ɻจࣈྻΛߴ࣍ݩϕΫτϧʹຒΊࠐΈɺDPTJOFڑͰྨࣅΛܭࢉ ࠾༻͠ͳ͔ͬͨཧ༝ ϦΞϧλΠϜಈըʹॏ͗͢ΔɻϞσϧ͕NBTUFSͷEPNBJOޠኮɺಛʹήʔϜޠΛΒͳ͍
ೖྗσʔλΛΩϨΠʹ͢Δࡉ͔͍ r Χˠྗ̍ͭΧλΧφͷΧΛࣈͷྗ ͔ͪΒ ʹஔ͖͑Δ ‣ ߈ܸʮྗʯͳͲܾ·ͬͨύλʔϯʹ͍ͭͯ r શ֯ɾ֯Λଗ͑Δ r
ۭനΛআڈ͢Δ Ϛονϯάͷલʹਖ਼نԽͯ͠ϊΠζΛআڈ͢Δ
ೝࣝΛ্ͤ͞Δࡉ͔͍ 0xD5D5EBF7EAD5D5EB ݩը૾ 9×8 grayscale 8×8 bit pattern 64-bit hash
E)BTIΛϋϛϯάڑͰൺֱͯ͠ྨࣅͷϑϨʔϜΛແࢹ͢Δ
None
·ͱΊ r Ϛελʔσʔλʢਖ਼ղͷఆٛʣΛ͑Δ r ϑϨʔϜ୯ҐͰޡೝࣝΛϑΟϧλʔ͢Δ ‣ .VMUJGSBNF$POTFOTVT r ࣄલʹೖྗΛΫϦʔϯʹ͢Δ ‣
ςΩετͷਖ਼نԽ ‣ Α͋͘ΔޡೝࣝΛஔ r ڍಈΛܾఆతɾ؍ଌՄೳʹ͢Δ ߴ͍࣭Ͱ࠶ݱੑͷ͋Δ݁ՌΛग़ྗ͢ΔͨΊͷ
3FTPVSDFT r IUUQTHJUIVCDPNLJTIJLBXBLBUTVNJ$BNFSB0$3 r IUUQTHJUIVCDPNLJTIJLBXBLBUTVNJ3FMJD'PSHF r IUUQTBQQTBQQMFDPNVTBQQSFMJDGPSHFJE r IUUQTSFMJDGPSHFQBHFTEFW