[python] opencv를 사용하여 이미지에 존재하는 모든 텍스트의 위치를 ​​가져옵니다.

텍스트 (숫자 및 알파벳)가 들어있는이 이미지가 있습니다. 이 이미지에있는 모든 텍스트와 숫자의 위치를 ​​얻고 싶습니다. 또한 모든 텍스트를 추출하고 싶습니다.

여기에 이미지 설명을 입력하십시오

내 이미지의 모든 텍스트 (숫자 및 알파벳)뿐만 아니라 좌표를 얻는 방법은 무엇입니까? 예를 들어 10B, 44, 16, 38, 22B 등



답변

텍스트가 아닌 윤곽선을 걸러 내기 위해 형태학 연산 을 사용하는 잠재적 인 접근 방식이 있습니다. 아이디어는 다음과 같습니다.

  1. 이진 이미지를 얻습니다. 이미지, 회색조, Otsu의 임계 값 로드

  2. 가로 및 세로 줄을 제거하십시오. 를 사용하여 가로 및 세로 커널을 cv2.getStructuringElement만든 다음cv2.drawContours

  3. 대각선, 원 개체 및 곡선 윤곽을 제거합니다.
    비 텍스트 윤곽을 분리하기 위해 윤곽 영역 cv2.contourArea
    및 윤곽 근사 cv2.approxPolyDP를 사용하여 필터링

  4. 텍스트 ROI 및 OCR을 추출하십시오. Pytesseract를 사용하여 윤곽선을 찾고 ROI를 필터링 한 다음 OCR을
    필터링하십시오 .


녹색으로 강조 표시된 수평선 제거

여기에 이미지 설명을 입력하십시오

수직선 제거

여기에 이미지 설명을 입력하십시오

여러 비 텍스트 윤곽선 (대각선, 원형 객체 및 곡선) 제거

여기에 이미지 설명을 입력하십시오

감지 된 텍스트 영역

여기에 이미지 설명을 입력하십시오

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()


답변

자, 여기 또 다른 가능한 해결책이 있습니다. 나는 당신이 파이썬으로 일한다는 것을 알고 있습니다-나는 C ++로 일합니다. 나는 당신에게 몇 가지 아이디어를 줄 것이며 희망한다면, 당신 이이 대답을 구현할 수 있기를 바랍니다.

주요 아이디어는 사용하지 않는 것입니다 사전 처리 (적어도하지 초기 단계에서) 전혀 대신 각 대상의 특성에 초점을 일부받을 특성필터링 이 속성에 따라 모든 방울을.

나는 1) 필터와 형태 단계가 얼룩의 품질을 떨어 뜨릴 수 있고 2) 대상 얼룩이 주로 가로 세로 비율면적으로 우리가 활용할 수있는 몇 가지 특성을 나타내는 것처럼 보이기 때문에 전처리를 사용하지 않습니다 .

그것을 확인하십시오, 숫자와 문자는 모두 더 큰 것보다 더 큰 것으로 보입니다 … 또한, 특정 지역 값 내에서 다양하게 나타납니다. 예를 들어, “너무 넓게” 또는 “너무 크게” 객체를 삭제하려고합니다 .

아이디어는 미리 계산 된 값에 속하지 않는 모든 것을 필터링한다는 것입니다. 문자 (숫자 및 문자)를 검사하고 최소, 최대 영역 값 및 최소 종횡비 (여기서는 높이와 너비의 비율)가 함께 제공되었습니다.

알고리즘 작업을 해보자. 이미지를 읽고 치수의 절반으로 크기를 조정하여 시작하십시오. 이미지가 너무 큽니다. 그레이 스케일로 변환하고 otsu를 통해 이진 이미지를 얻으십시오. 여기 의사 코드가 있습니다.

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

멋있는. 이 이미지로 작업하겠습니다. 모든 흰색 얼룩을 검사하고 “속성 필터”를 적용해야합니다 . 통계와 함께 연결된 구성 요소를 사용하여 각 블롭을 루프하고 면적과 종횡비를 얻습니다 .C ++에서는 다음과 같이 수행됩니다.

//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors  color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs

};

이제 속성 필터를 적용하겠습니다. 이것은 미리 계산 된 임계 값과의 비교 일뿐입니다. 다음 값을 사용했습니다.

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

for루프 내 에서 현재 블롭 속성을이 값과 비교하십시오. 테스트가 긍정적이면 블롭 검정색을 “페인트”합니다. for루프 내부에서 계속 :

    //Filter your blobs

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

루프 후 필터링 된 이미지를 구성하십시오.

cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at<int>(y, x);
        filteredMat.at<cv::Vec3b>(y, x) = colors[label];
    }
}

그리고… 거의 다 왔습니다. 찾고있는 것과 유사하지 않은 모든 요소를 ​​필터링했습니다. 알고리즘을 실행하면 다음과 같은 결과가 나타납니다.

여기에 이미지 설명을 입력하십시오

또한 결과를 더 잘 시각화하기 위해 blob의 경계 상자를 찾았습니다.

여기에 이미지 설명을 입력하십시오

보시다시피 일부 요소가 감지되지 않습니다. 찾고있는 문자를 더 잘 식별 할 수 있도록 “속성 필터”를 세분화 할 수 있습니다. 약간의 머신 러닝을 포함하는보다 심층적 인 솔루션을 위해서는 “이상적인 특징 벡터”를 구축하고 얼룩에서 특징을 추출하고 유사성 측정을 통해 두 벡터를 비교해야합니다. 또한 사후 처리를 적용 하여 결과를 향상시킬 수 있습니다 …

어쨌든, 당신의 문제는 사소하지도 않고 쉽게 확장 할 수 없으며, 나는 단지 당신에게 아이디어를주고 있습니다. 다행히도 솔루션을 구현할 수 있기를 바랍니다.


답변

한 가지 방법은 슬라이딩 윈도우를 사용하는 것입니다 (비싸다).

이미지의 문자 크기를 결정하고 (모든 문자가 이미지에서 보이는 것과 동일한 크기 임) 창의 크기를 설정하십시오. 감지를 위해 tesseract를 시도하십시오 (입력 이미지에는 사전 처리가 필요함). 창에서 문자를 연속적으로 감지하면 창의 좌표를 저장하십시오. 좌표를 병합하고 문자의 영역을 가져옵니다.


답변