原文:Tesseract OCR Tutorial for iOS
作者:Lyndsey Scott
译者:kmyhy

更新说明:本教程由 Lyndsey Scott 更新为 Swift 4、iOS 11 和 Xcode 9。原文作者是 Lyndsey Scott。

你肯定知道 OCR……它通常用于处理扫描文档,手写文稿,以及在 Google 的 Translate app 所用的实景翻译技术。今天你将学习如何在你自己的 app 中利用 Tesseract 来实现它。听起来很不错,是吗?

但是……什么是 OCR?

光学文字识别(OCR)是一种从图片中抽取数字化字符的过程。当抽取完成后,用户就可以将这些文字用于编辑文档、文字搜索、压缩等等。

在本教程中,你将用 OCR 去追求你的真爱。你将使用一个由 Google 维护的开源 OCR 引擎 Tesseract 创建一个名为 Love In A Snap 的 app。这个 app 允许你用一首爱情诗的图片作为素材,将原作者的女神/男神替换为你想追求的对象。好棒!准备让人们大吃一惊吧。

开始

从这里下载开始项目,并解压缩。

这里面有几个文件夹:

  • LoveInASnap: 开始项目。
  • Images:爱情诗图片。
  • tessdata: Tesseract 的语言包。

打开 LoveInASnap\LoveinASnap.xcodeproj,build & run,随意点点,感受一下 UI。目前的 app 很简单,但你会在选中或反选文本框时看到会上移下移。这是为了防止键盘遮住文字框和按钮。

开始编写代码

打开 ViewController.swift 看一下代码。你会看到几个 @IBOutlet 属性和 @IBAction 方法已经连接到了 Main.storyboard。在这些 @IBAction 中,view.endEditing(true) 用于释放键盘。在 sharePoen(_:) 方法中这样做是因为当键盘弹出时,分享按钮会被遮挡住。

在这些 @IBAction 之后,你会看到一个 performImageRecognition(_:)。这是 Tesseract 进行图片识别的地方。

下面两个函数用于将视图上移、下移:

func moveViewUp() {if topMarginConstraint.constant != originalTopMargin {return}  topMarginConstraint.constant -= 135UIView.animate(withDuration: 0.3) {self.view.layoutIfNeeded()}
}func moveViewDown() {if topMarginConstraint.constant == originalTopMargin {return}topMarginConstraint.constant = originalTopMarginUIView.animate(withDuration: 0.3) {self.view.layoutIfNeeded()}
}

当键盘弹出时,moveViewUp 将 View controller 的 view 的 top 约束向上移。当键盘收起,moveViewDown 将控制器视图的 top 约束设置回原来的值。

在故事板中,UITextField 的委托设置为 ViewController。在 UITextFieldDelegate 扩展中有这几个方法:

// MARK: - UITextFieldDelegate
extension ViewController: UITextFieldDelegate {func textFieldDidBeginEditing(_ textField: UITextField) {moveViewUp()}func textFieldDidEndEditing(_ textField: UITextField) {moveViewDown()}
}

当用户开始编辑 text field 时,调用 moveViewUp。当用户结束编辑 text field时,调用 moveViewDown。

尽管上述函数对于 app UX 来说必不可少,但跟本文毫不相关。因为它们是已经写好的,我们可以直接从真正感兴趣的代码入手。

Tesseract 的限制

Tesseract OCR 非常强大,但也有一些限制:

  • 和别的 OCR 引擎不同(比如美国邮政服务用于整理邮件的 OCR),Tesseract 无法识别手写体。实际上,它总共只支持 64 中字体。
  • 可以通过对图像进行预处理来提升 Tesseract 的性能。你必须通过对图片进行缩放、增加颜色对比度、对文本水平对齐来优化处理结果。
  • 最后,Tesseract OCR 只支持 Linux、Windows、和 Mac OS X。

呃?有 Linux、Windows 和 Mac OS X,没有 iOS?幸运的是,gali8 对 Tesseract OCR 进行了一个 O-C 的封装,你可以在 Swift 和 iOS 中使用。

嘁!:]

安装 Tesseract

根据 Joshua Greene 写的一篇教程如何在 Swift 中使用 CocoaPods 所描述的,你可以用以下步骤安装 CocoaPods 和 Tesseract 框架。

要安装 CocoaPods,可以在终端中使用命令:

sudo gem install cocoapods

当问到计算机密码时,请输入正确的密码。

要在项目中安装 Tesseract,用 cd 命令转到 LoveInASnap 项目所在的目录。例如,如果你的开始项目位于桌面,请使用:

cd ~/Desktop/OCR_Tutorial_Resources/LoveInASnap

然后,用下列命令在这个文件夹下生成一个 Podfile 文件:

pod init

用文本编辑器打开 Podfile 文件,编辑内容为:

use_frameworks!
platform :ios, '11.0'target 'LoveInASnap' douse_frameworks!pod 'TesseractOCRiOS'
end

这会告诉 CocoaPods 你想在项目中使用 TesseractOCRiOS 框架。最后,保存、关闭 Podfiel,进入终端,保持之前的工作目录不变,输入命令:

pod install

就是这样!当一段长长的输出之后,然后你会看到 “Please close any current Xcode sessions and use ‘LoveInASnap.xcworkspace’ for this project from now on.” 。关闭 LoveinASnap.xcodeproj,在 Xcode 中打开OCR_Tutorial_Resources\LoveInASnap\LoveinASnap.xcworkspace 。

在 Xcode 中设置 Tesseract

将 tessdata 文件夹,也就是 Tesseract 的语言包,从 Finder 中拖进 Xcode 项目的 Supporting Files 文件夹下。确认勾选 Copy items 选项,和 Create folder 选项,然后勾上 LoveInASnap,点击 Finish。

注意:确认在 Build Phases 的 Copy Bundlle Resources 下面有 tessdata 一项,否者运行时会报错,说在 tessdata 的父目录中未设置 TESSDATA_PREFIX 环境变量。

返回项目导航器,点击 LoveInASnap 项目文件,在 Targets 下面,选择 LoveInASnap,打开 General 标签页,找到 Linked Frameworkds and Libraries 选项。

这里只应该有一个文件存在:Pods_LoveInASnap.framework,也就是你刚刚添加的那个 pod。点击 + 按钮,添加 libstadc++.dylib、CoreImage.framework 和 TesseractOCR.framework。

之后,你的 Linked Frameworks and Libraries 应该变成:

差不多了!还剩一个步骤,我们就可以开始编写代码了……

在 LoveInASnap target 的 Build Settings 中,找到 C++ Standard Library,将它设置为 Compiler Default。然后找到 Enable Bitcode,将它设置为 NO。

类似地,回到左边的项目导航器中,选择 Pods 项目,找到 TesseractOCRiOS target 的 Build Settings,找到 C++ Standard Library 将它设置为 Compiler Default。然后找到 Enable Bitcoe 将它设置为 NO。

就是这样了!Build & run,确保能够编译。你会在左边的 issue 导航器中看到一些警告,但不要理它们。

好了没有?现在你终于可以开始有意思的部分了!

创建 Image Picker

打开 ViewController.swift 在类定义之后添加扩展:

// 1
// MARK: - UINavigationControllerDelegate
extension ViewController: UINavigationControllerDelegate {
}// MARK: - UIImagePickerControllerDelegate
extension ViewController: UIImagePickerControllerDelegate {func presentImagePicker() {// 2let imagePickerActionSheet = UIAlertController(title: "Snap/Upload Image",message: nil, preferredStyle: .actionSheet)// 3if UIImagePickerController.isSourceTypeAvailable(.camera) {let cameraButton = UIAlertAction(title: "Take Photo",style: .default) { (alert) -> Void inlet imagePicker = UIImagePickerController()imagePicker.delegate = selfimagePicker.sourceType = .cameraself.present(imagePicker, animated: true)}imagePickerActionSheet.addAction(cameraButton)}// Insert here}
}

代码解释如下:

  1. 将 ViewController 声明为实现 UINavigationControllerDelegate 和 UIImagePickerController 协议,这是使用 UIImagePickerController 时必须实现的两个协议。
  2. 在 presentImagePicker() 方法中,创建一个 UIAlertController 用于向用户显示一个 action sheet 以便获取用户的选择。
  3. 如果设备拥有摄像头,在 imagePickerActionSheet 中添加一个 Take Photo 按钮。这个按钮会用 .camera 作为 sourceType 来创建和呈现 UIImagePickerController。

为了完成这个函数,请将 // Insert here 替换为:

// 1
let libraryButton = UIAlertAction(title: "Choose Existing",style: .default) { (alert) -> Void inlet imagePicker = UIImagePickerController()imagePicker.delegate = selfimagePicker.sourceType = .photoLibraryself.present(imagePicker, animated: true)
}
imagePickerActionSheet.addAction(libraryButton)
// 2
let cancelButton = UIAlertAction(title: "Cancel", style: .cancel)
imagePickerActionSheet.addAction(cancelButton)
// 3
present(imagePickerActionSheet, animated: true)

代码解释:

  1. 在 imagePickerActionSheet 中添加 Choose Existing 按钮。这个按钮用 .photoLibrary 作为 sourceType 来创建和呈现 UIImagePickerController。
  2. 添加一个 Cancel 按钮。
  3. 呈现 UIAlertController。

然后,在 takePhoto(_:) 方法中添加:

presentImagePicker()

当你点击 Snap/Upload Image 时,这会显示一个 Image picker。

如果你用真机编译,并企图拍照,app 会崩溃。因为 app 没有向用户获取到相机访问权限,因此你还需要添加必要的权限声明字段。

声明访问相册权限

在项目导航器中,找到 LoveInASnap 的 Info.plist 文件。在 Information Property List 上面点击 + 按钮,添加 Privacy – Photo Library Usage Description 和 Privacy – Camera Usage Description 两个 key。将它们的值填写为要显示给用户的内容。

Build & run。点击 Snap/Upload Image,你将看到 UIAlertController 显示出来:

注意:如果你使用模拟器,因为没有真实摄像头,你将无法看到 Take Photo 这个选项。

如果你点击 Take Photo,然后授权 app 访问相机,你就可以进行拍照。如果你选择 Choose Existing 然后授权 app 访问相册,你就可以从中选择一张图片。

选择图片之后,app 目前是不会做任何动作的。你还需要在 Tesseract 处理图片之前做一些准备工作。

在 Tesseract 的限制中提到,为了优化 OCR 的结果,你必须将图片尺寸限制在一定大小。如果图片太大或者太小,Tesseract 可能返回错误结果或者出现 EXC_BAD_ACCESS 而崩溃。

因此你必须写一个修改图片大小但宽高比保持不变的方法。

维持纵横比缩放图片

图片的纵横比是指它的宽度和高度的比例。因此,在减少图片的尺寸同时不改变纵横比,你必须将这个宽高比作为一个常量。

如果你知道原始图片的宽和高,那么只要你知道最终图片的宽或高中的任意一个,就能够应用下面的纵横比公式:

因此,height2 = height1/width1 * width2,width2 = width1/height1 * height2。你可以在缩放方法中用这两个公式来保持图片的纵横比不变。

打开 ViewController.swift 在 UIImage 扩展中添加方法:

// MARK: - UIImage extension
extension UIImage {func scaleImage(_ maxDimension: CGFloat) -> UIImage? {var scaledSize = CGSize(width: maxDimension, height: maxDimension)if size.width > size.height {let scaleFactor = size.height / size.widthscaledSize.height = scaledSize.width * scaleFactor} else {let scaleFactor = size.width / size.heightscaledSize.width = scaledSize.height * scaleFactor}UIGraphicsBeginImageContext(scaledSize)draw(in: CGRect(origin: .zero, size: scaledSize))let scaledImage = UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()return scaledImage}
}

scaleImage(_:) 方法会获取图片的高或者宽——比较两者的较大者为准——然后将它的大小设置为 maxDimension 参数。然后,为了维持图片的纵横比,根据需要缩放另一边即可。然后将原图重新在新 frame 中重绘。最后,返回缩放后的图片。

现在,你必须写一个方法获取用户选择的图片。

获取图片

在 UIImagePickerControllerDelegate 扩展中在 presentImagePicker() 下面添加方法:

// 1
func imagePickerController(_ picker: UIImagePickerController,didFinishPickingMediaWithInfo info: [String : Any]) {// 2if let selectedPhoto = info[UIImagePickerControllerOriginalImage] as? UIImage, let scaledImage = selectedPhoto.scaleImage(640) {// 3activityIndicator.startAnimating()// 4dismiss(animated: true, completion: {self.performImageRecognition(scaledImage)})}
}

这个方法解释如下:

  1. imagePickerController(_:didFinishPickingMediaWithInfo:) 是 UIImagePickerControllerDelegate 协议中的方法。当用户选择好图片,这个方法会在一个 info 字典中返回这张图片的信息。
  2. 将图片通过 UIImagePickerControllerOriginalImage 键从 info 字典中取出。将图片缩放至宽高小于 640。(根据经验,640 的识别结果最佳)同时对缩放后的图片进行解包操作。
  3. 让 activity indicator 开始显示,表示 Tesseract 正在工作。
  4. 解散 UIImagePicker,将图片传递给 performImageRecognition 方法处理。

Build & run,点击 Snap/upload Image,选择一张图片。activity indicator 将开始旋转。

别被它迷花了眼!还有更多代码要写。

我们显示了 activity indicator,但它到底代表什么意思?闲话少说(请来点掌声),你终于可以开始使用 Tesseract OCR 了!

使用 Tesseracdt OCR

打开 ViewController.swift 在 import UIKit 下添加:

import TesseractOCR

这将导入 Tesseract 框架,并允许你在这个文件中使用它。

然后,在 performImageRecognition(_:) 方法一开始添加:

// 1
if let tesseract = G8Tesseract(language: "eng+fra") {// 2tesseract.engineMode = .tesseractCubeCombined// 3tesseract.pageSegmentationMode = .auto// 4tesseract.image = image.g8_blackAndWhite()// 5tesseract.recognize()// 6textView.text = tesseract.recognizedText
}
// 7
activityIndicator.stopAnimating()

OCR 开始发挥作用了!整个方法分为以下几个部分:

  1. 创建一个 G8Tesseract 对象,传入 eng+fra 参数,即英语和法语语言包。本教程中所用的诗中用到了一些法语(罗曼蒂克),因此添加法语能让 Tesseract 认识其中的法语单词,并形成合体的字符。
  2. 有 3 个 OCR 模式:.tesseractOnly 最快,但准确率是最差的。.cubeOnly,稍慢但准确率更高,因为它使用了更多的人工智能。.tesseractdCubeCombined 集合了 .tesseractOnly 和 .cubeOnly,这也是其中最慢的一种模式。在本教程中,使用了.tesseractCubeCombined,因为它的准确率最高。
  3. Tesseract 默认要处理的文本处于同一文本块中。因为例子中使用的诗包含了段落换行符,它不是同一文本块。将 pageSegmentationMode 设置为 .auto 允许 Tesseract 自动识别出段落之间的分隔。
  4. 当文本和背景之间的对比度越高,识别的结果越好。用 Tesseract 内置的 g8_blackAndWhite 滤镜降低颜色饱和度,增加对比度,降低曝光度。
  5. 进行光学文字识别。
  6. 将识别出的文字放到 textview 里。
  7. 移除 activity indicator,表示 OCR 过程结束。

是时候测试一下代码,看看什么结果了!

处理第一张图片

在示例图片中有这样一张图片 OCR_Tutorial_Resources\Images\Lenore.png:

Lenore.png 包含了一首爱情诗,是寄给 “Lenore” 的,但只需要稍微编辑下,就能用于送给你的女神/男神!:]

如果在有相机的设备上运行 app,你可以拍下这首诗,然后进行 OCR。但出于本文演示目的,将图片添加到设备的相机胶卷中,你就能够上传它了。这样,你可以避免光源不均匀、文字倾斜、打印不清晰等问题。

如果你使用模拟器,将图片文件拖进模拟器,即可将它添加到你的相机胶卷。

Build & run,选择 Snap/Upload Image,然后选择 Choose Existing。同意 app 访问你的相册,然后选择这张图片。

然后……看到了吗!几秒钟之后,文字就识别出来并显示到了 text view 中。

只不过,如果你的女神/男神名字并不叫做 Lenore,他或者她并不会买账。因为在诗中,Lenore 的使用十分频繁,要将它替换成你的心上人是一个不小的工作。

你说什么?是的,你可以写一个函数,查找并替换这个词。这想法太妙了!下一节将告诉你怎么做。

查找替换文本

现在 OCR 引擎已经把图片转换成文字,你可以把它看成普通的字符串对待。

还记得吗?ViewController.swift 中有一个 swapText 函数,当 swap 按钮被点时会触发这个函数。这就简单了,是不?

找到 swapText(_:),在 view.endEditing(true) 一句下面添加:

// 1
guard let text = textView.text,let findText = findTextField.text,let replaceText = replaceTextField.text else {return
}

// 2
textView.text =text.replacingOccurrences(of: findText, with: replaceText)
// 3
findTextField.text = nil
replaceTextField.text = nil

这段代码很简单,让我们来简单过一下:

  1. 判断 textView、findTextField 和 replaceTextField 中内容不为空时,才调用交换方法。
  2. 在 text view 中,将 findTextField 中指定的文本替换为 replaceTextField 中的内容。
  3. 替换完成,清除 findTextField 和 replaceTextField 中的内容。

Build & run,再次上传示例图片,让 Tesseract 开始工作。当文字显示出来后,在 Find this … 中输入 Lenore ,在 Repace with … 中输入你的男神/女神的名字(注意,查找替换是大小写敏感的)。点击 swap 按钮,完成替换。

变,变,变-你创作了一首为情人量身定制的爱情诗!

你还可以替换其它单词,以迸发出你自己的艺术火花!

太好了!这么有诗意和勇气的作品不应该只呆在你的手机里。你还需要一个方法将你的大作分享给全世界。

分享成果

要分享你的诗,请在 sharePeom() 中编写代码:

// 1
if textView.text.isEmpty {return
}
// 2
let activityViewController = UIActivityViewController(activityItems:[textView.text], applicationActivities: nil)
// 3
let excludeActivities:[UIActivityType] = [.assignToContact,.saveToCameraRoll,.addToReadingList,.postToFlickr,.postToVimeo]
activityViewController.excludedActivityTypes = excludeActivities
// 4
present(activityViewController, animated: true)

分别解释如下:

  1. 如果 text view 是空的,返回。
  2. 否则,用 text view 中的文本初始化一个 UIActivityViewController。
  3. UIActivityViewController 的 activity 类型默认是一个很长的数组。这里我们将不相关的类型全部排除。
  4. 呈现 UIActivityViewController 允许用户将他们的创作按照希望的方式进行分析。

再次 build & run。上传示例图片,查找替换文字。然后欣赏完你的诗作之后,点击信封,会显示分享选项,然后将你的诗歌按照你想要的方式分享出去。

这样你的 Love In A Snap 就完成了——你肯定能够获得对方的青睐。

你可以像我一样,将 Lenore 换掉,将诗歌发送到你的收件箱,然后独自饮下一杯葡萄酒,带着惺忪的眼神,假装这封 email 是来自于女王陛下,为了一次特别大气的、充满浪漫、舒适、美妙和神秘的夜晚……但还是只有我一个……

接下来做什么

从这里下载完成后的开始项目。

你可以看看 GitHub 上的 Tesseract 的 iOS 封装:https://github.com/gali8/Tesseract-OCR-iOS。在 Google 的 Tesseract OCR 网站可以下载其他语言包(请使用 3.03 版本以上的语言包,以便和当前框架兼容)。

在后续研究 OCR 的时候,记住这点:“输入的质量越差,输出的结果就越差。”最简单的提升输出质量的方法是改善输入的质量,比如:

  • 对图片进行预处理。
  • 对图片反复进行滤镜处理,比较结果上的差异,然后得到最准确的输出。
  • 创建自己的 AI 逻辑,比如神经网络。
  • 用 Tesseract 自己的训练工具帮助你的程序从错误中的学习,并即时改进成功率。

通过联合多种策略,你会得到最好的结果,因此请尝试各种手段并找出最佳工作方式。

最后,如果你对本文、Tesseract 或者 OCR 有任何问题或建议,请在下面留言。

Tesseract OCR iOS 教程相关推荐

  1. iOS实践:OpenCV、Tesseract OCR结合 识别图片中文字

    前言: 前天领导问,类似扫描文件识别图中文字的功能如何实现,找一下第三方的开源库,尝试下,于是有了这篇文章: 分析: 识别场景中,识别身份证信息当属典型,查阅了几篇文章,后续的实现中也多导入了其代码: ...

  2. Tesseract OCR 下载及安装教程 (中英文语言包)

    Tesseract OCR 下载安装 (中英文语言包) (需要csdn币的下载真没必要,所有的包都在这里免费下) https://github.com/tesseract-ocr/tessdata 这 ...

  3. Tesseract OCR——Windows 10 + CMake-GUI + Visual Studio 2019下编译和使用解决方案

    基本概念 Tesseract OCR:Tesseract-OCR 引擎最先由HP实验室于1985年开始研发,至1995年时已经成为OCR业内最准确的三款识别引擎之一.然而,HP不久便决定放弃OCR业务 ...

  4. ​Xamarin iOS教程之自定义视图

    ​Xamarin iOS教程之自定义视图 Xamarin iOS自定义视图 工具栏中的视图在实际应用开发中用的很多,但是为了吸引用户的眼球,开发者可以做出一些自定义的视图. [示例2-33]以下将实现 ...

  5. Xamarin iOS教程之警告视图

    Xamarin iOS教程之警告视图 Xamarin iOS警告视图 如果需要向用户显示一条非常重要的消息时,警告视图(UIAlertView类)就可以派上用场了.它的功能是把需要注意的信息显示给用户 ...

  6. Xamarin iOS教程之页面控件

    Xamarin iOS教程之页面控件 Xamarin iOS 页面控件 在iPhone手机的主界面中,经常会看到一排小白点,那就是页面控件,如图2.44所示.它是由小白点和滚动视图组成,可以用来控制翻 ...

  7. Xamarin iOS教程之进度条和滚动视图

    Xamarin iOS教程之进度条和滚动视图 Xamarin iOS 进度条 进度条可以看到每一项任务现在的状态.例如在下载的应用程序中有进度条,用户可以很方便的看到当前程序下载了多少,还剩下多少.Q ...

  8. Xamarin iOS教程之键盘的使用和设置

    Xamarin iOS教程之键盘的使用和设置 Xamarin iOS使用键盘 在文本框和文本视图中可以看到,当用户在触摸这些视图后,就会弹出键盘.本节将主要讲解键盘的输入类型定义.显示键盘时改变输入视 ...

  9. Xamarin iOS教程之显示和编辑文本

    Xamarin iOS教程之显示和编辑文本 Xamarin iOS显示和编辑文本 在一个应用程序中,文字是非常重要的.它就是这些不会说话的设备的嘴巴.通过这些文字,可以很清楚的指定这些应用程序要表达的 ...

  10. ​Xamarin iOS教程之视图显示图像

    ​Xamarin iOS教程之视图显示图像 Xamarin iOS显示图像 在主视图中显示一个图像,可以让开发者的应用程序变的更有趣,例如,在一些应用程序开始运行时,都会通过图像来显示此应用程序的玩法 ...

最新文章

  1. 八种基本类型的包装类你真的懂了?
  2. 用python画烟花-python 实现漂亮的烟花,樱花,玫瑰花
  3. c#-----让richtextbox或者TextBox不可编辑
  4. 计算机主机安装系统安装系统,系统重装
  5. 天勤数据结构:前缀、中缀、后缀表达式的转换与计算
  6. linux l显示详细信息,fdisk -l显示信息详解
  7. python 数学公式显示_ipython jupyter notebook中显示图像和数学公式实例
  8. JAVA中,如果发现一个值起作用,却又没找到哪里使用,检查一下是否有native/JNI中反射
  9. 启动mongodb时发现错误libcrypto.so.10
  10. Python # 金十数据数字货币新闻爬取脚本
  11. ssm整合的简单案例(附源码)
  12. 域名未授权 / 该网站未授权,禁止使用 解决办法:
  13. Python——全国二级等级考试
  14. 微软认知服务应用秘籍 – 支持跨平台客户端的视觉服务中间层
  15. python plt图片保存emf类型_matplotlib---保存图片出现的问题
  16. vimdiff 命令使用介绍
  17. 如何实现背景/背景图片透明文字不透明
  18. Visual Studio界面颜色更换 及 Visual Assist X助手使用
  19. 移动互联风口频现,百度高德谁已手握地图关键钥匙
  20. 用代码写一个表白biu小心心

热门文章

  1. 文件下载---txt文件下载
  2. Spine 1.73+ 和谐版
  3. 约瑟夫环c语言代码顺序存储,约瑟夫环问题算法的C语言代码实现
  4. 2013年最新省市区三级联动mysql数据库_省市区三级联动菜单(附数据库)
  5. 20200408_W_水波理论和波浪载荷
  6. opencore 启动总是在win_OpenCore引导开机倒计时自动进入指定系统盘,修改默认启动项教程...
  7. 分享一个嘉立创封装库(内含AD和PADS两种格式)
  8. java 汉字 正则_java正则表达式验证汉字
  9. 白话空间统计之二十五:空间权重矩阵(三)解构空间权重矩阵
  10. 数据库系统概论——事务