• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

SwiftyTesseract/SwiftyTesseract: A Swift wrapper around Tesseract for use in iOS ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

SwiftyTesseract/SwiftyTesseract

开源软件地址(OpenSource Url):

https://github.com/SwiftyTesseract/SwiftyTesseract

开源编程语言(OpenSource Language):

Swift 99.1%

开源软件介绍(OpenSource Introduction):

SwiftyTesseract

This library is no longer maintained and will see no further updates.

I created this project out of a need to perform OCR on labels and machine readable formats in a warehouse environment. It served that purpose well enough, however I have not worked on that project in over 3 years and have not personally used this project since.

If you need OCR support in your application, I suggest you use the first party option by using the Text Recognition capabilities of Apple's Vision framework. If your language is not supported by Apple, I suggest you fork this project and maintain it yourself. If you need assistance migrating to another solution or in maintaining your own fork, you or your company can reach out to me to arrange a contract agreement.


SPM compatible swift-version platforms

GitHub Workflow Status Examples Status Linux ARM and macOS M1 Build Badge

Table of Contents

Version Compatibility

SwiftyTesseract Version Platforms Supported Swift Version
4.x.x iOS macOS Linux 5.3
3.x.x iOS 5.0 - 5.2
2.x.x iOS 4.2
1.x.x iOS 4.0 - 4.1

Known Issue Submitting to App Store Connect

When submitting to App Store Connect, libtesseract.framework will need to be removed from your app bundle before submission. This can be achieved through a post-build action in your application target's scheme by running the following:

rm -rf "${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Frameworks/libtesseract.framework"

If you are facing this error after already building your project, you will need to clear your derived data and perform a clean build.

This issue currently affects all binary Swift packages and is not unique to this project. Please see SwiftyTesseract issue #83 and libtesseract issue #3 for more information.

Develop

Develop should be considered unstable and API breaking changes could happen at any time. If you need to utilize some changes contained in develop, adding the specific commit is highly recommended:

.package(
    url: "https://github.com/SwiftyTesseract/SwiftyTesseract.git",
    // This is just an example of a commit hash, do not just copy and paste this into your Package.swift
    .revision("0e0c6aca147add5d5750ecb7810837ef4fd10fc2")
)

SwiftyTesseract 3.x.x Support

4.0.0 contains a lot of major breaking changes and there have been issues when migrating from Xcode 11 to 12 with versions 3.x.x. The support/3.x.x branch has been created to be able to address any issues for those who are unable or unwilling to migrate to the latest version. This branch is only to support blocking issues and will not see any new features.

Support for Cocoapods and Carthage Dropped

As the Swift Package Manager improves year over year, I have been decided to take advantage of binary Swift Packages that were announced during WWDC 2020 to eliminate having the dependency files being built ad-hoc and served out of the main source repo. This also has the benefit for being able to support other platforms via Swift Package Manager like Linux because the project itself is no longer dependent on Tesseract being vendored out of the source repository. While I understand this may cause some churn with existing projects that rely on SwiftyTesseract as a dependency, Apple platforms themselves have their own first-party OCR support through the Vision APIs.

SwiftyTesseract class renamed to Tesseract

The SwiftyTesseract class name felt a bit verbose and is more descriptive of the project than the class itself. To disambiguate between Google's Tesseract project and SwiftyTesseract's Tesseract class, all mentions of the class will be displayed as a code snippet: Tesseract.

Using SwiftyTesseract in Your Project

Import the module

import SwiftyTesseract

There are two ways to quickly instantiate SwiftyTesseract without altering the default values. With one language:

let tesseract = Tesseract(language: .english)

Or with multiple languages:

let tesseract = Tesseract(languages: [.english, .french, .italian])

Performing OCR

Platform Agnostic

Pass an instance of Data derived from an image to performOCR(on:)

let imageData = try Data(contentsOf: urlOfYourImage)
let result: Result<String, Tesseract.Error> = tesseract.performOCR(on: imageData)

Combine

Pass an instance of Data derived from an image to performOCRPublisher(on:)

let imageData = try Data(contentsOf: urlOfYourImage)
let result: AnyPublisher<String, Tesseract.Error> = tesseract.performOCRPublisher(on: imageData)

UIKit

Pass a UIImage to the performOCR(on:) or performOCRPublisher(on:) methods:

let image = UIImage(named: "someImageWithText.jpg")!
let result: Result<String, Error> = tesseract.performOCR(on: image)
let publisher: AnyPublisher<String, Error> = tesseract.performOCRPublisher(on: image)

AppKit

Pass a NSImage to the performOCR(on:) or performOCRPublisher(on:) methods:

let image = NSImage(named: "someImageWithText.jpg")!
let result: Result<String, Error> = tesseract.performOCR(on: image)
let publisher: AnyPublisher<String, Error> = tesseract.performOCRPublisher(on: image)

Conclusion

For people who want a synchronous call, the performOCR(on:) method provides a Result<String, Error> return value and blocks on the thread it is called on.

The performOCRPublisher(on:) publisher is available for ease of performing OCR in a background thread and receiving results on the main thread (only available on iOS 13.0+ and macOS 10.15+):

let cancellable = tesseract.performOCRPublisher(on: image)
  .subscribe(on: backgroundQueue)
  .receive(on: DispatchQueue.main)
  .sink(
    receiveCompletion: { completion in 
      // do something with completion
    },
    receiveValue: { string in
      // do something with string
    }
  )

The publisher provided by performOCRPublisher(on:) is a cold publisher, meaning it does not perform any work until it is subscribed to.

Extensibility

The major downside to the pre-4.0.0 API was it's lack of extensibility. If a user needed to set a variable or perform an operation that existed in the Google Tesseract API but didn't exist on the SwiftyTesseract API, the only options were to fork the project or create a PR. This has been remedied by creating an extensible API for Tesseract variables and Tesseract functions.

Tesseract Variable Configuration

Starting in 4.0.0, all public instance variables of Tesseract have been removed in favor of a more extensible and declarative API:

let tesseract = Tesseract(language: .english) {
  set(.disallowlist, "@#$%^&*")
  set(.minimumCharacterHeight, .integer(35))
  set(.preserveInterwordSpaces, .true)
}
// or
let tesseract = Tesseract(language: .english)
tesseract.configure {
  set(.disallowlist, "@#$%^&*")
  set(.minimumCharacterHeight, .integer(35))
  set(.preserveInterwordSpaces, .true)
}

The pre-4.0.0 API looks like this:

let swiftyTesseract = SwiftyTesseract(language: .english)
swiftyTesseract.blackList = "@#$%^&*"
swiftyTesseract.minimumCharacterHeight = 35
swiftyTesseract.preserveInterwordSpaces = true

Tesseract.Variable

Tesseract.Variable is a new struct introduced in 4.0.0. It's definition is quite simple:

extension Tesseract {
  public struct Variable: RawRepresentable {
    public init(rawValue: String) {
      self.init(rawValue)
    }
    
    public init(_ rawValue: String) {
      self.rawValue = rawValue
    }
    
    public let rawValue: String
  }
}

extension Tesseract.Variable: ExpressibleByStringLiteral {
  public typealias StringLiteralType = String

  public init(stringLiteral value: String) {
    self.init(value)
  }
}

// Extensions containing the previous API variables available as members of SwiftyTesseract
public extension Tesseract.Variable {
  static let allowlist: Tesseract.Variable = "tessedit_char_whitelist"
  static let disallowlist: Tesseract.Variable = "tessedit_char_blacklist"
  static let preserveInterwordSpaces: Tesseract.Variable = "preserve_interword_spaces"
  static let minimumCharacterHeight: Tesseract.Variable = "textord_min_xheight"
  static let oldCharacterHeight: Tesseract.Variable = "textord_old_xheight"
}

The problem here is that the library doesn't cover all the cases. What if you wanted to set Tesseract to only recognize numbers? You may be able to set the allowlist to only recognize numerals, but the Google Tesseract API already has a variable that does that: "classify_bln_numeric_mode".

Extending the library to make use of that variable could look something like this:

tesseract.configure {
  set("classify_bln_numeric_mode", .true)
}
// Or extend Tesseract.Variable to get a clean trailing dot syntax:
// Using ExpressibleByStringLiteral conformance
extension Tesseract.Variable {
  static let numericMode: Tesseract.Variable = "classify_bln_numeric_mode"
}
// Using initializer
extension Tesseract.Variable {
  static let numericMode = Tesseract.Variable("classify_bln_numeric_mode")
}

tesseract.configure {
  set(.numericMode, .true)
}

perform(action:)

Another issue that I've seen come up several times is "Can you implement X Tesseract feature" as a feature request. This has the same implications as the old property-based accessors for setting Tesseract variables. The perform(action:) method allows users full access to the Tesseract API in a thread-safe manner.

This comes with one major caveat: You will be completely responsible for managing memory when dealing with the Tessearct API directly. Using the Tesseract C API means that ARC will not help you. If you use this API directly, make sure your instrument your code and check for leaks. Swift's defer functionality pairs really well with managing memory when dealing directly with C APIs; check out Sources/SwiftyTesseract/Tesseract+OCR.swift for examples of using defer to release memory.

All of the library methods provided on Tesseract other than Tesseract.perform(action:) and Tesseract.configure(_:) are implemented as extensions using only Tesseract.perform(action:) to access the pointer created during initialization. To see this in action see the implementation of performOCR(on:) in Sources/SwiftyTesseract/Tesseract+OCR.swift

As an example, let's implement issue #66 using perform(action:):

import SwiftyTesseract
import libtesseract

public typealias PageSegmentationMode = TessPageSegMode
public extension PageSegmentationMode {
  static let osdOnly = PSM_OSD_ONLY
  static let autoOsd = PSM_AUTO_OSD
  static let autoOnly = PSM_AUTO_ONLY
  static let auto = PSM_AUTO
  static let singleColumn = PSM_SINGLE_COLUMN
  static let singleBlockVerticalText = PSM_SINGLE_BLOCK_VERT_TEXT
  static let singleBlock = PSM_SINGLE_BLOCK
  static let singleLine = PSM_SINGLE_LINE
  static let singleWord = PSM_SINGLE_WORD
  static let circleWord = PSM_CIRCLE_WORD
  static let singleCharacter = PSM_SINGLE_CHAR
  static let sparseText = PSM_SPARSE_TEXT
  static let sparseTextOsd = PSM_SPARSE_TEXT_OSD
  static let count = PSM_COUNT
}

public extension Tesseract {
  var pageSegmentationMode: PageSegmentationMode {
    get {
      perform { tessPointer in
        TessBaseAPIGetPageSegMode(tessPointer)
      }
    }
    set {
      perform { tessPointer in
        TessBaseAPISetPageSegMode(tessPointer, newValue)
      }
    }
  }
}

// usage
tesseract.pageSegmentationMode = .singleColumn

If you don't care about all of the boilerplate needed to make your call site feel "Swifty", you could implement it simply like this:

import SwiftyTesseract
import libtesseract

extension Tesseract {
  var pageSegMode: TessPageSegMode {
    get {
      perform { tessPointer in
        TessBaseAPIGetPageSegMode(tessPointer)
      }
    }
    set {
      perform { tessPointer in
        TessBaseAPISetPageSegMode(tessPointer, newValue)
      }
    }
  }
}

// usage
tesseract.pageSegMode = PSM_SINGLE_COLUMN

ConfigurationBuilder

The declarative configuration syntax is achieved by accepting a function builder with functions that have a return value of (TessBaseAPI) -> Void. Using the previous example of extending the library to set the page segmentation mode of Tesseract, you could also create a function with a return signature of (TessBaseAPI) -> Void to utilize the declarative configuration block either during initialization or through Tesseract.configure(:_):

import SwiftyTesseract
import libtesseract

func setPageSegMode(_ pageSegMode: TessPageSegMode) -> (TessBaseAPI) -> Void {
  return { tessPointer in
    TessBaseAPISetPageSegMode(tessPointer, pageSetMode)
  }
}

let tesseract = Tesseract(language: .english) {
  setPageSegMode(PSM_SINGLE_COLUMN)
}
// or post initialization
tesseract.configure {
  setPageSegMode(PSM_SINGLE_COLUMN)
}

(The information for what to implement for this example was found in the Tesseract documentation)

Conclusion

The major feature of 4.0.0 is it's lack of features. The core of Tesseract is less than 130 lines of code, with the remainder of the code base implemented as extensions. I have attempted to be as un-opinionated as possible while providing an API that feels right at home in Swift. Users of the library are not limited to what I have time for or what other contributors to the project are able to contribute. Now that this API is available, additions to the API surface of the library will be very selective. There should no longer be any restrictions to users of the library given the extensibility.

A Note on Initializer Defaults

The full signature of the primary Tesseract initializer is

public init Tesseract(
  languages: [RecognitionLanguage], 
  dataSource: LanguageModelDataSource = Bundle.main, 
  engineMode: EngineMode = .lstmOnly,
  @ConfigurationBuilder configure: () -> (TessBaseAPI) -> Void = { { _ in } }
)

The bundle parameter is required to locate the tessdata folder. This will need to be changed if Tesseract is not being implemented in your application bundle or if you are developing a Swift Package project (in this case you would need to specify Bundle.module, see Tests/SwiftyTesseractTests/SwiftyTesseractTests.swift for an example). The engine mode dictates the type of .traineddata files to put into your tessdata folder. .lstmOnly was chosen as a default due to the higher speed and reliability found during testing, but could potentially vary depending on the language being recognized as well as the image itself. See Which Language Training Data Should You Use? for more information on the different types of .traineddata files that can be used with SwiftyTesseract

libtesseract

Tesseract and it's dependencies are now built and distributed as an xcframework under the SwiftyTesseract/libtesseract repository for Apple platforms. Any issues regarding the build configurations for those should be raised under that repository.

Installation

Swift Package Manager is now the only supported dependency manager for bringing SwiftyTesseract into your project.

Apple Platforms

// Package.swift
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
  name: "AwesomePackage",
  platforms: [
    // These are the minimum versions libtesseract supports
    .macOS(.v10_13),
    .iOS(.v11),
  ],
  products: [
    .library(
      name: "AwesomePackage",
      targets: ["AwesomePackage"]
    ),
  ],
  dependencies: [
    .package(url: "https://github.com/SwiftyTesseract/SwiftyTesseract.git", .upToNextMajor(from: "4.0.0"))
  ],
  targets: [
    .target(
      name: "AwesomePackage",
      dependencies: ["SwiftyTesseract"]
    ),
  ]
)


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap