OpenCV+OCR 影象處理字元識別原理及程式碼
需配置好OpenCV和OCR環境下執行
1、OpenCV簡介
OpenCV的全稱是Open Source Computer Vision Library,是一個跨平臺的計算機視覺庫。
OpenCV用C++語言編寫,它的主要介面也是C++語言,但是依然保留了大量的C語言介面。該庫也有大量的Python, Java and MATLAB/OCTAVE (版本2.5)的介面。這些語言的API介面函式可以通過線上文件獲得。現在也提供對於C#, Ruby的支援。
它有以下特點:
1) 開放的C/C++原始碼
2) 基於Intel處理器指令集開發的優化程式碼
3) 統一的結構和功能定義
4) 強大的影象和矩陣運算能力
5) 方便靈活的使用者介面
6)同時支援MS-WINDOWS、LINUX平臺
作為一個基本的計算機視覺、影象處理和模式識別的開源專案,OPENCV可以直接應用於很多領域,作為第二次開發的理想工具。
2、OCR簡介
OCR (Optical Character Recognition,光學字元識別)是指電子裝置(例如掃描器或數碼相機)檢查紙上列印的字元,通過檢測暗、亮的模式確定其形狀,然後用字元識別方法將形狀翻譯成計算機文字的過程;即,對文字資料進行掃描,然後對影象檔案進行分析處理,獲取文字及版面資訊的過程。如何除錯或利用輔助資訊提高識別正確率,是OCR最重要的課題,ICR(Intelligent Character Recognition)的名詞也因此而產生。衡量一個OCR系統性能好壞的主要指標有:拒識率、誤識率、識別速度、使用者介面的友好性,產品的穩定性,易用性及可行性等。
3、OpenCV.jar包對影象實現灰度化原理
我們知道,在一個24位彩色影象中,每個畫素由三個位元組表示,通常表示為RGB。通常,許多24位彩色影象儲存為32點陣圖像,每個畫素多餘的位元組儲存為一個alpha值,表現有特殊影響的資訊。在RGB模型中,如果R=G=B時,則彩色表示一種灰度顏色,其中R=G=B的值叫灰度值,因此,灰度影象每個畫素只需一個位元組存放灰度值(又稱強度值、亮度值),灰度範圍為0-255。這樣就得到一幅圖片的灰度圖。
幾種灰度化的方法
①、分量法
將彩色影象中的三分量的亮度作為三個灰度影象的灰度值,可根據應用需要選取一種灰度影象。
F1(i,j) = R(i,j) F2(i,j) = G(i,j) F3(i,j) = B(i,j)
程式碼示例:
import cv2.cv as cv
image = cv.LoadImage('mao.jpg')
b = cv.CreateImage(cv.GetSize(image), image.depth, 1)
g = cv.CloneImage(b)
r = cv.CloneImage(b)
cv.Split(image, b, g, r, None)
cv.ShowImage('a_window', r)
cv.WaitKey(0)
②、 最大值法
將彩色影象中的三分量亮度的最大值作為灰度圖的灰度值。
F(i,j) = max(R(i,j), G(i,j), B(i,j))
程式碼示例:
image = cv.LoadImage('mao.jpg')
new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):
for j in range(image.width):
new[i,j] = max(image[i,j][0], image[i,j][1], image[i,j][2])
cv.ShowImage('a_window', new)
cv.WaitKey(0)
③、平均值法
將彩色影象中的三分量亮度求平均得到一個灰度值。
F(i,j) = (R(i,j) + G(i,j) + B(i,j)) / 3
程式碼示例:
image = cv.LoadImage('mao.jpg')
new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):
for j in range(image.width):
new[i,j] = (image[i,j][0] + image[i,j][1] + image[i,j][2])/3
cv.ShowImage('a_window', new)
cv.WaitKey(0)
④、加權平均法
根據重要性及其它指標,將三個分量以不同的權值進行加權平均。由於人眼對綠色的敏感最高,對藍色敏感最低,因此,按下式對RGB三分量進行加權平均能得到較合理的灰度影象。
F(i,j) = 0.30R(i,j) + 0.59G(i,j) + 0.11B(i,j))
程式碼示例:
image = cv.LoadImage('mao.jpg')
new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):
for j in range(image.width):
new[i,j] = 0.3 * image[i,j][0] + 0.59 * image[i,j][1] + 0.11 * image[i,j][2]
cv.ShowImage('a_window', new)
cv.WaitKey(0)
上面的公式可以看出綠色(G 分量)所佔的比重比較大,所以有時候也會直接取G 分量進行灰度化。
程式碼示例:
image = cv.LoadImage('mao.jpg')
new = cv.CreateImage(cv.GetSize(image), image.depth, 1)for i in range(image.height):
for j in range(image.width):
new[i,j] = image[i,j][1]
cv.ShowImage('a_window', new)
cv.WaitKey(0)
而OpenCV的Java實現中採用的是加權法來實現圖片的灰度化。
4、OpenCV.jar包對影象進行二值化處理原理
影象的二值化處理就是將影象上的點的灰度置為0或255,也就是將整個影象呈現出明顯的黑白效果。即將256個亮度等級的灰度影象通過適當的閾值選取而獲得仍然可以反映影象整體和區域性特徵的二值化影象。在數字影象處理中,二值影象佔有非常重要的地位,特別是在實用的影象處理中,以二值影象處理實現而構成的系統是很多的,要進行二值影象的處理與分析,首先要把灰度影象二值化,得到二值化影象,這樣子有利於在對影象做進一步處理時,影象的集合性質只與畫素值為0或255的點的位置有關,不再涉及畫素的多級值,使處理變得簡單,而且資料的處理和壓縮量小。為了得到理想的二值影象,一般採用封閉、連通的邊界定義不交疊的區域。所有灰度大於或等於閾值的畫素被判定為屬於特定物體,其灰度值為255表示,否則這些畫素點被排除在物體區域以外,灰度值為0,表示背景或者例外的物體區域。如果某特定物體在內部有均勻一致的灰度值,並且其處在一個具有其他等級灰度值的均勻背景下,使用閾值法就可以得到比較的分割效果。如果物體同背景的差別表現不在灰度值上(比如紋理不同),可以將這個差別特徵轉換為灰度的差別,然後利用閾值選取技術來分割該影象。動態調節閾值實現影象的二值化可動態觀察其分割影象的具體結果。
5、OpenCV.jar包對影象進行腐蝕處理原理
對二值圖腐蝕過程:
在下圖中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),中間是結構元素B,那個標有origin的點是中心點,即當前處理元素的位置。腐蝕的方法是,拿B的中心點和X上的點一個一個地對比,如果B上的所有點(指的是所有黑點)都在X的範圍內(即X圖上處理元素所在的位置以及它上,左兩個點都是黑色),則該點保留,否則將該點去掉(變為白點);右邊是腐蝕後的結果。可以看出,它仍在原來X的範圍內,且比X包含的點要少,就像X被腐蝕掉了一層。
對灰度影象的腐蝕:
如下圖,左邊是要處理影象,中間是結構元素,右邊是與對應每個畫素的灰度值。
處理過程就是:與上面的B一樣,中間是要處理的元素所在的位置,三個1所在的位置對應三個灰度值,然後將中間這個1對應的灰度值改成這三個最小的,如源影象第一個灰度值1,它上左都沒有灰度值,所以最小就是它本身,所以輸出也是1,再比如處理灰度值為22那個點的時候,上面是7左邊是44,所以22應改為7。
6、OCR識別提取圖片中文字原理
· 預處理:對包含文字的影象進行處理以便後續進行特徵提取、學習。這個過程的主要目的是減少影象中的無用資訊,以便方便後面的處理。在這個步驟通常有:灰度化(如果是彩色影象)、降噪、二值化、字元切分以及歸一化這些子步驟。經過二值化後,影象只剩下兩種顏色,即黑和白,其中一個是影象背景,另一個顏色就是要識別的文字了。降噪在這個階段非常重要,降噪演算法的好壞對特徵提取的影響很大。字元切分則是將影象中的文字分割成單個文字——識別的時候是一個字一個字識別的。如果文字行有傾斜的話往往還要進行傾斜校正。歸一化則是將單個的文字影象規整到同樣的尺寸,在同一個規格下,才能應用統一的演算法。
· 特徵提取和降維:特徵是用來識別文字的關鍵資訊,每個不同的文字都能通過特徵來和其他文字進行區分。對於數字和英文字母來說,這個特徵提取是比較容易的,因為數字只有10個,英文字母只有52個,都是小字符集。對於漢字來說,特徵提取比較困難,因為首先漢字是大字符集,國標中光是最常用的第一級漢字就有3755個;第二個漢字結構複雜,形近字多。在確定了使用何種特徵後,視情況而定,還有可能要進行特徵降維,這種情況就是如果特徵的維數太高(特徵一般用一個向量表示,維數即該向量的分量數),分類器的效率會受到很大的影響,為了提高識別速率,往往就要進行降維,這個過程也很重要,既要降低維數吧,又得使得減少維數後的特徵向量還保留了足夠的資訊量(以區分不同的文字)。
· 分類器設計、訓練和實際識別:分類器是用來進行識別的,就是對於第二步,對一個文字影象,提取出特徵給,丟給分類器,分類器就對其進行分類,告訴你這個特徵該識別成哪個文字。
· 後處理:後處理是用來對分類結果進行優化的,第一個,分類器的分類有時候不一定是完全正確的(實際上也做不到完全正確),比如對漢字的識別,由於漢字中形近字的存在,很容易將一個字識別成其形近字。後處理中可以去解決這個問題,比如通過語言模型來進行校正——如果分類器將“在哪裡”識別成“存哪裡”,通過語言模型會發現“存哪裡”是錯誤的,然後進行校正。第二個,OCR的識別影象往往是有大量文字的,而且這些文字存在排版、字型大小等複雜情況,後處理中可以嘗試去對識別結果進行格式化,比如按照影象中的排版排列什麼的,舉個栗子,一張影象,其左半部分的文字和右半部分的文字毫無關係,而在字元切分過程中,往往是按行切分的,那麼識別結果中左半部分的第一行後面會跟著右半部分的第一行諸如此類。
程式碼:
TestOcr類:
package com.njupt.yangmaohu;
import java.io.File;
import java.io.IOException;
public class TestOcr {
public static void main(String[] args) {
// TODO 自動生成的方法存根
//輸入圖片地址
String path = "G:/ka.jpg";
PictureManage pictureManage = new PictureManage(path); //對圖片進行處理
pictureManage.imshow();
try {
String valCode = new OCR().recognizeText(new File("xintu.jpg"), "jpg");//jpg是圖片格式
System.out.println("圖片中文字為:"+"\n"+valCode);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
PictureManage類:
package com.njupt.yangmaohu;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
public class PictureManage {
private Mat image;
//private JLabel jLabelImage;
public PictureManage(String fileName) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
this.image= Highgui.imread(fileName);
}
/**
* 圖片畫質處理
* @param image
* @return
*/
public static Mat setMatImage(Mat image) {
Mat loadeMatImage = new Mat();
//灰度處理
Imgproc.cvtColor(image,image,Imgproc.COLOR_RGB2GRAY);
//二值化處理
Mat binaryMat = new Mat(image.height(), image.width(), CvType.CV_8UC1);
Imgproc.threshold(image, binaryMat,20, 300, Imgproc.THRESH_BINARY);
//影象腐蝕
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT,
new Size(500,500));
Imgproc.erode(binaryMat, image,element);
//loadeMatImage = image;
loadeMatImage = binaryMat;
return loadeMatImage;
}
/**
* Mat轉image
* @param matrix
* @return
*/
private Image toBufferedImage(Mat matrix) {
int type = BufferedImage.TYPE_BYTE_GRAY;
if (matrix.channels()>1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = matrix.channels()*matrix.cols()*matrix.rows();
byte[] buffer = new byte[bufferSize];
matrix.get(0, 0, buffer);
BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(),type);
final byte[] targetPxiels = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
System.arraycopy(buffer, 0, targetPxiels, 0, buffer.length);
return image;
}
/***
* 將Image變數儲存成圖片
* @param im
* @param fileName
*/
public void saveImage(Image im ,String fileName) {
int w = im.getWidth(null);
int h = im.getHeight(null);
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
Graphics g = bi.getGraphics();
g.drawImage(im, 0, 0, null);
try {
ImageIO.write(bi, "jpg", new File(fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 圖片處理
* @param args
*/
public void imshow(){
//新增原圖
Image originalImage = toBufferedImage(image);
saveImage(originalImage, "yuantu.jpg");
//jLabelImage.setIcon(new ImageIcon(originalImage));
//新增處理圖
Mat mat1 = setMatImage(image);
Image newImage = toBufferedImage(mat1);
saveImage(newImage, "xintu.jpg");
}
}
ImageIOHelper類:
package com.njupt.yangmaohu;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
public class ImageIOHelper {
/**
* 圖片檔案轉換為tif格式
* @param imageFile 檔案路徑
* @param imageFormat 副檔名
* @return
*/
public static File createImage(File imageFile, String imageFormat) {
File tempFile = null;
try {
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(imageFormat);
ImageReader reader = readers.next();
ImageInputStream iis = ImageIO.createImageInputStream(imageFile);
reader.setInput(iis);
//Read the stream metadata
IIOMetadata streamMetadata = reader.getStreamMetadata();
//Set up the writeParam
TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.CHINESE);
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
//Get tif writer and set output to file
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff");
ImageWriter writer = writers.next();
BufferedImage bi = reader.read(0);
IIOImage image = new IIOImage(bi,null,reader.getImageMetadata(0));
tempFile = tempImageFile(imageFile);
ImageOutputStream ios = ImageIO.createImageOutputStream(tempFile);
writer.setOutput(ios);
writer.write(streamMetadata, image, tiffWriteParam);
ios.close();
writer.dispose();
reader.dispose();
} catch (IOException e) {
e.printStackTrace();
}
return tempFile;
}
private static File tempImageFile(File imageFile) {
String path = imageFile.getPath();
StringBuffer strB = new StringBuffer(path);
strB.insert(path.lastIndexOf('.'),0);
return new File(strB.toString().replaceFirst("(?<=//.)(//w+)$", "tif"));
}
}
OCR類:
package com.njupt.yangmaohu;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.jdesktop.swingx.util.OS;
public class OCR {
private final String LANG_OPTION = "-l"; //英文字母小寫l,並非數字1
private final String EOL = System.getProperty("line.separator");
private String tessPath = "C://Program Files (x86)//Tesseract-OCR";//tesseract-ocr安裝地址
//private String tessPath = new File("tesseract").getAbsolutePath();
public String recognizeText(File imageFile,String imageFormat)throws Exception{
File tempImage = ImageIOHelper.createImage(imageFile,imageFormat);
File outputFile = new File(imageFile.getParentFile(),"output");
StringBuffer strB = new StringBuffer();
List<String> cmd = new ArrayList<String>();
if(OS.isWindowsXP()){
cmd.add(tessPath+"//tesseract");
}else if(OS.isLinux()){
cmd.add("tesseract");
}else{
cmd.add(tessPath+"//tesseract");
}
cmd.add("");
cmd.add(outputFile.getName());
//cmd.add(LANG_OPTION);
//cmd.add("chi_sim");
//cmd.add("eng");
ProcessBuilder pb = new ProcessBuilder();
pb.directory(imageFile.getParentFile());
cmd.set(1, tempImage.getName());
pb.command(cmd);
pb.redirectErrorStream(true);
Process process = pb.start();
//tesseract.exe 1.jpg 1 -l chi_sim
int w = process.waitFor();
//刪除臨時正在工作檔案
tempImage.delete();
if(w==0){
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+".txt"),"UTF-8"));
String str;
while((str = in.readLine())!=null){
strB.append(str).append(EOL);
}
in.close();
}else{
String msg;
switch(w){
case 1:
msg = "Errors accessing files.There may be spaces in your image's filename.";
break;
case 29:
msg = "Cannot recongnize the image or its selected region.";
break;
case 31:
msg = "Unsupported image format.";
break;
default:
msg = "Errors occurred.";
}
tempImage.delete();
//throw new RuntimeException(msg);
}
new File(outputFile.getAbsolutePath()+".txt").delete();
return strB.toString();
}
}