รับตำแหน่งของข้อความทั้งหมดที่อยู่ในภาพโดยใช้ opencv


11

ฉันมีภาพนี้ที่มีข้อความ (ตัวเลขและตัวอักษร) อยู่ในนั้น ฉันต้องการรับตำแหน่งของข้อความและตัวเลขทั้งหมดที่อยู่ในภาพนี้ นอกจากนี้ฉันต้องการแยกข้อความทั้งหมดด้วย

ป้อนคำอธิบายรูปภาพที่นี่

ฉันจะรับลูกสมุนตลอดจนข้อความทั้งหมด (ตัวเลขและตัวอักษร) ในภาพของฉันได้อย่างไร เช่น 10B, 44, 16, 38, 22B เป็นต้น


รุ่นของคุณคืออะไร หากเวอร์ชั่นของคุณคือ 2.1 ให้ลองติดตั้ง 2.0
gellezzz

1
การโยนหัวด้วยคำถามที่ไม่ดีเป็นการฝึกฝนที่ไม่ดี คุณไม่มีความรู้เกี่ยวกับวิธีการทำเช่นนี้ดูเหมือนว่าคุณเพียงแค่พยายามหลอกล่อให้นักพัฒนาเขียนโปรแกรมเป็นโซลูชั่นที่สมบูรณ์เพื่อแลกกับตัวแทนบางจุด ฉันไม่ได้คาดหวังว่าจะเห็นคำตอบที่สมบูรณ์แบบสำหรับเหตุผลนั้น แต่ฉันเชื่อว่าคุณจะได้รับโซลูชั่นที่ดีกว่าในเว็บไซต์ฟรีแลนซ์หากคุณจ่ายเงินให้กับผู้คนในเวลาของพวกเขา
karlphillip

@karlphillip ขอโทษ แต่ฉันเป็นผู้เริ่มต้นฉันต้องการบางสิ่งบางอย่างที่จะเริ่มต้นใช่มั้ย? คุณช่วยฉันออกมาได้ไหม
Pulkit Bhatnagar

คำตอบ:


13

นี่เป็นวิธีที่มีศักยภาพในการใช้รูปทรงสัณฐานเพื่อกรองรูปทรงที่ไม่ใช่ข้อความ ความคิดคือ:

  1. รับภาพไบนารี โหลดภาพระดับสีเทา ตามด้วยเกณฑ์ของ Otsu

  2. ลบเส้นแนวนอนและแนวตั้ง สร้างเมล็ดในแนวนอนและแนวตั้งโดยใช้cv2.getStructuringElementจากนั้นลบบรรทัดด้วยcv2.drawContours

  3. ลบเส้นทแยงมุมวัตถุวงกลมและรูปทรงโค้ง กรองโดยใช้พื้นที่รูปร่างcv2.contourArea และการประมาณรูปร่างcv2.approxPolyDP เพื่อแยกรูปทรงที่ไม่ใช่ข้อความ

  4. แยก ROIs ข้อความและ OCR ค้นหารูปทรงและตัวกรองสำหรับ ROIs แล้ว OCR ใช้ Pytesseract


ลบเส้นแนวนอนที่เน้นด้วยสีเขียว

ป้อนคำอธิบายรูปภาพที่นี่

ลบเส้นแนวตั้ง

ป้อนคำอธิบายรูปภาพที่นี่

นำส่วนที่ไม่ใช่ข้อความออก (เส้นทแยงมุมวัตถุทรงกลมและเส้นโค้ง)

ป้อนคำอธิบายรูปภาพที่นี่

ตรวจพบขอบเขตข้อความ

ป้อนคำอธิบายรูปภาพที่นี่

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()

ความคิดที่ดีที่จะลบบรรทัดเหล่านั้นออกก่อน
karlphillip

ความคิดที่ไม่ดีที่จะลบบางส่วนของตัวอักษร ...
Walter Tross

8

เอาล่ะนี่เป็นอีกวิธีที่เป็นไปได้ ฉันรู้ว่าคุณทำงานกับ Python - ฉันทำงานกับ C ++ ฉันจะให้ความคิดและหวังว่าหากคุณต้องการคุณจะสามารถใช้คำตอบนี้ได้

แนวคิดหลักคือไม่ใช้การประมวลผลล่วงหน้าเลย (อย่างน้อยไม่ได้อยู่ในขั้นเริ่มต้น) และแทนที่จะมุ่งเน้นไปที่ตัวละครเป้าหมายแต่ละตัวรับคุณสมบัติบางอย่างและกรองทุกหยดตามคุณสมบัติเหล่านี้

ฉันพยายามที่จะไม่ใช้การประมวลผลก่อนเนื่องจาก 1) การกรองและขั้นตอนก้านอาจลดคุณภาพของ blobs และ 2) blobs เป้าหมายของคุณดูเหมือนจะแสดงลักษณะบางอย่างที่เราสามารถใช้ประโยชน์จากส่วนใหญ่: อัตราส่วนและพื้นที่

ลองดูสิตัวเลขและตัวอักษรทั้งหมดดูเหมือนจะสูงกว่ากว้างกว่า ... นอกจากนี้พวกมันดูเหมือนจะแปรผันไปตามค่าของพื้นที่ ตัวอย่างเช่นคุณต้องการที่จะทิ้งวัตถุ"กว้างเกินไป"หรือ"ใหญ่เกินไป"

แนวคิดคือฉันจะกรองทุกอย่างที่ไม่ได้อยู่ในค่าที่คำนวณไว้ล่วงหน้า ฉันตรวจสอบอักขระ (ตัวเลขและตัวอักษร) และมาพร้อมกับค่าพื้นที่ต่ำสุดค่าสูงสุดและอัตราส่วนต่ำสุด (ที่นี่คืออัตราส่วนระหว่างความสูงและความกว้าง)

มาทำงานกับอัลกอริทึมกัน เริ่มต้นด้วยการอ่านภาพและปรับขนาดให้เล็กลงครึ่งหนึ่ง ภาพของคุณใหญ่เกินไป แปลงเป็นระดับสีเทาและรับภาพไบนารีผ่าน 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];
    }
}

และ…มันก็สวยมาก คุณกรององค์ประกอบทั้งหมดที่ไม่คล้ายกับสิ่งที่คุณกำลังมองหา ใช้อัลกอริทึมที่คุณได้รับผลลัพธ์นี้:

ป้อนคำอธิบายรูปภาพที่นี่

ฉันได้ค้นพบกล่อง Bounding Boxes ของ blobs เพิ่มเติมเพื่อให้เห็นภาพผลลัพธ์ที่ดีขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

อย่างที่คุณเห็นองค์ประกอบบางอย่างถูกตรวจพบพลาด คุณสามารถปรับแต่ง "ตัวกรองคุณสมบัติ" เพื่อระบุตัวละครที่คุณกำลังมองหาได้ดียิ่งขึ้น โซลูชันที่ลึกกว่าซึ่งเกี่ยวข้องกับการเรียนรู้ของเครื่องจักรเพียงเล็กน้อยจำเป็นต้องมีการสร้าง "เวกเตอร์คุณลักษณะที่สมบูรณ์แบบ" การแยกคุณสมบัติจาก blobs และเปรียบเทียบเวกเตอร์ทั้งสองผ่านการวัดความคล้ายคลึงกัน นอกจากนี้คุณยังสามารถใช้การประมวลผลภายหลังเพื่อปรับปรุงผลลัพธ์ ...

ไม่ว่าอะไรก็ตามปัญหาของคุณไม่ใช่เรื่องง่ายและปรับขนาดได้ง่ายและฉันแค่ให้ความคิดกับคุณ หวังว่าคุณจะสามารถใช้โซลูชันของคุณได้


โอกาสใดก็ตามที่คุณสามารถแปลงโปรแกรมเดียวกันเป็น python
Pulkit Bhatnagar

@PulkitBhatnagar ใช่แน่นอน คุณเพียงแค่จับแน่นฉันจะมีพอร์ตที่สมบูรณ์แบบพร้อมในไม่กี่นาที
eldesgraciado

?? คุณทำไปแล้วเพื่อที่ฉันจะได้มอบรางวัลให้คุณ
Pulkit Bhatnagar

อ่าใช่ ฉันขอโทษอย่างสุดซึ้งที่รักของฉันฉันพบปัญหาบางอย่าง แต่การกลับใจใหม่กำลังดำเนินไปอย่างดี แค่รอ ขอบคุณ.
eldesgraciado

ไม่เคยสงสัยเลยว่าอาจเป็นการเสียดสี
Pulkit Bhatnagar

4

วิธีหนึ่งคือการใช้หน้าต่างเลื่อน (มันแพง)

กำหนดขนาดของตัวละครในภาพ (ตัวละครทุกตัวมีขนาดเท่ากันกับที่เห็นในภาพ) และกำหนดขนาดของหน้าต่าง ลอง tesseract สำหรับการตรวจจับ (ภาพอินพุตต้องประมวลผลล่วงหน้า) หากหน้าต่างตรวจพบอักขระต่อเนื่องให้จัดเก็บพิกัดของหน้าต่าง ผสานพิกัดและรับภูมิภาคกับตัวละคร


ฉันคิดว่า 100 รางวัลสำหรับคำตอบ
Himanshu Poddar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.