web-dev-qa-db-ja.com

画像処理と文字の抽出

文字の画像を処理するために必要なテクノロジーを把握しようとしています。

具体的には、この例では、円で囲まれたハッシュタグを抽出する必要があります。あなたはそれをここで見ることができます:

enter image description here

どんな実装も大いに役立ちます。

21
jkushner

この問題は OpenCV + Tesseract で解決できます。

もっと簡単な方法があるかもしれませんが。 OpenCVは、コンピュータービジョンアプリケーションの構築に使用されるオープンソースライブラリであり、TesseractはオープンソースのOCRエンジンです。

始める前に、はっきりさせておきましょう。それは円ではなく、その角丸四角形です。

問題を解決する方法を示すために書いたアプリケーションのソースコードと、何が起こっているのかについてのヒントを共有しています。この回答は、デジタル画像処理について誰にも教育するものではなく、読者がこの分野について最小限の理解を持っていることが期待されています。

コードのより大きなセクションが何をするかを非常に簡単に説明します。次のコードの大部分は、OpenCVに同梱されているサンプルアプリケーションである squares.cpp からのものです。

#include <iostream>
#include <vector>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

// angle: helper function.
// Finds a cosine of angle between vectors from pt0->pt1 and from pt0->pt2.
double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 )
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

// findSquares: returns sequence of squares detected on the image.
// The sequence is stored in the specified memory storage.
void findSquares(const cv::Mat& image, std::vector<std::vector<cv::Point> >& squares)
{  
    cv::Mat pyr, timg;

    // Down-scale and up-scale the image to filter out small noises
    cv::pyrDown(image, pyr, cv::Size(image.cols/2, image.rows/2));
    cv::pyrUp(pyr, timg, image.size());

    // Apply Canny with a threshold of 50
    cv::Canny(timg, timg, 0, 50, 5);

    // Dilate canny output to remove potential holes between Edge segments
    cv::dilate(timg, timg, cv::Mat(), cv::Point(-1,-1));

    // find contours and store them all as a list 
    std::vector<std::vector<cv::Point> > contours;           
    cv::findContours(timg, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

    for( size_t i = 0; i < contours.size(); i++ ) // Test each contour
    {
        // Approximate contour with accuracy proportional to the contour perimeter
        std::vector<cv::Point> approx;   
        cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true);

        // Square contours should have 4 vertices after approximation
        // relatively large area (to filter out noisy contours)
        // and be convex.
        // Note: absolute value of an area is used because
        // area may be positive or negative - in accordance with the
        // contour orientation
        if( approx.size() == 4 &&
            fabs(cv::contourArea(cv::Mat(approx))) > 1000 &&
            cv::isContourConvex(cv::Mat(approx)) )
        {
            double maxCosine = 0;

            for (int j = 2; j < 5; j++)
            {
                // Find the maximum cosine of the angle between joint edges
                double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                maxCosine = MAX(maxCosine, cosine);
            }

            // If cosines of all angles are small
            // (all angles are ~90 degree) then write quandrange
            // vertices to resultant sequence
            if( maxCosine < 0.3 )
                squares.Push_back(approx);
        }
    }         
}


// drawSquares: function draws all the squares found in the image
void drawSquares( cv::Mat& image, const std::vector<std::vector<cv::Point> >& squares )
{
    for( size_t i = 0; i < squares.size(); i++ )
    {
        const cv::Point* p = &squares[i][0];
        int n = (int)squares[i].size();
        cv::polylines(image, &p, &n, 1, true, cv::Scalar(0,255,0), 2, CV_AA);
    }

    cv::imshow("drawSquares", image);
}

では、プログラムは次の場所から始まります。

int main(int argc, char* argv[])
{
// Load input image (colored, 3-channel)
cv::Mat input = cv::imread(argv[1]);
if (input.empty())
{
    std::cout << "!!! failed imread()" << std::endl;
    return -1;
}   

// Convert input image to grayscale (1-channel)
cv::Mat grayscale = input.clone();
cv::cvtColor(input, grayscale, cv::COLOR_BGR2GRAY);
//cv::imwrite("gray.png", grayscale);

grayscaleは次のようになります:

// Threshold to binarize the image and get rid of the shoe
cv::Mat binary;
cv::threshold(grayscale, binary, 225, 255, cv::THRESH_BINARY_INV);
cv::imshow("Binary image", binary);
//cv::imwrite("binary.png", binary);

binaryは次のようになります:

// Find the contours in the thresholded image
std::vector<std::vector<cv::Point> > contours;
cv::findContours(binary, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

// Fill the areas of the contours with BLUE (hoping to erase everything inside a rectangular shape)
cv::Mat blue = input.clone();      
for (size_t i = 0; i < contours.size(); i++)
{
    std::vector<cv::Point> cnt = contours[i];
    double area = cv::contourArea(cv::Mat(cnt));               

    //std::cout << "* Area: " << area << std::endl; 
    cv::drawContours(blue, contours, i, cv::Scalar(255, 0, 0), 
                     CV_FILLED, 8, std::vector<cv::Vec4i>(), 0, cv::Point() );         
}       

cv::imshow("Countours Filled", blue);  
//cv::imwrite("contours.png", blue);  

blueは次のようになります。

// Convert the blue colored image to binary (again), and we will have a good rectangular shape to detect
cv::Mat gray;
cv::cvtColor(blue, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, binary, 225, 255, cv::THRESH_BINARY_INV);
cv::imshow("binary2", binary);
//cv::imwrite("binary2.png", binary);

この時点でbinaryは次のようになります。

// Erode & Dilate to isolate segments connected to nearby areas
int erosion_type = cv::MORPH_RECT; 
int erosion_size = 5;
cv::Mat element = cv::getStructuringElement(erosion_type, 
                                            cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1), 
                                            cv::Point(erosion_size, erosion_size));
cv::erode(binary, binary, element);
cv::dilate(binary, binary, element);
cv::imshow("Morphologic Op", binary); 
//cv::imwrite("morpho.png", binary);

この時点でbinaryは次のようになります。

// Ok, let's go ahead and try to detect all rectangular shapes
std::vector<std::vector<cv::Point> > squares;
findSquares(binary, squares);
std::cout << "* Rectangular shapes found: "  << squares.size() << std::endl;

// Draw all rectangular shapes found
cv::Mat output = input.clone();
drawSquares(output, squares);
//cv::imwrite("output.png", output);

outputは次のようになります。

よし!丸みを帯びた長方形を見つける問題の最初の部分を解決しました。上の画像を見ると、長方形が検出され、教育目的で元の画像の上に緑色の線が引かれていることがわかります。

2番目の部分ははるかに簡単です。まず、元の画像にROI(Region of Interest)を作成して、丸みを帯びた長方形の内側の領域に画像をトリミングできるようにします。これが完了すると、トリミングされた画像がTIFFファイルとしてディスクに保存され、それがTesseractに供給されて魔法になります。

// Crop the rectangular shape
if (squares.size() == 1)
{    
    cv::Rect box = cv::boundingRect(cv::Mat(squares[0]));
    std::cout << "* The location of the box is x:" << box.x << " y:" << box.y << " " << box.width << "x" << box.height << std::endl;

    // Crop the original image to the defined ROI
    cv::Mat crop = input(box);
    cv::imshow("crop", crop);
    //cv::imwrite("cropped.tiff", crop);
}
else
{
    std::cout << "* Abort! More than one rectangle was found." << std::endl;
}

// Wait until user presses key
cv::waitKey(0);

return 0;
}

cropは次のようになります。

このアプリケーションがジョブを完了すると、ディスク上にcropped.tiffという名前のファイルが作成されます。コマンドラインに移動し、Tesseractを呼び出して、トリミングされた画像に存在するテキストを検出します。

tesseract cropped.tiff out

このコマンドは、検出されたテキストを含むout.txtという名前のファイルを作成します。

enter image description here

Tesseractには、アプリケーションにOCR機能を追加するために使用できるAPIがあります。

このソリューションは堅牢ではなく、他のテストケースでも機能するように、あちこちでいくつかの変更を行う必要があります。

53
karlphillip

いくつかの選択肢があります: Java OCR実装

彼らは次のツールについて言及します:

そして他のいくつか。

このリンクのリストも役立ちます: http://www.javawhat.com/showCategory.do?id=21380

一般に、この種のタスクには多くの試行錯誤が必要です。おそらく、最良のツールは、何よりも入力データのプロファイルに大きく依存します。

3
Lajos Veres

この記事を確認できます: http://www.codeproject.com/Articles/196168/Contour-Analysis-for-Image-Recognition-in-C

Contour Analysis Demo

数学の理論とC#での実装が付属しています(残念ながら、Java)+ opencvで実装する場合は書き直す必要はありません。そのため、Visual Studioを使用して再構築する必要がありますあなたがそれをテストしたいならあなたのopencvバージョンに対して、しかしそれは価値があります。

3
Dabo

OCRはスキャンされたドキュメントでうまく機能します。あなたが参照しているのは、一般的な画像でのテキスト検出であり、他の手法が必要です(フローの一部としてOCRが使用される場合があります)。

「プロダクション対応」の実装については知りません。

一般的な情報については、「画像内のテキスト検出」でGoogle Scholarを試してください。

私にとってうまくいった特定の方法は 'stroke width transform' (SWT)実装するのは難しくなく、オンラインで利用できる実装もいくつかあると思います。

0
Ophir Yoktan