แยกข้อความ OpenCV


148

ฉันพยายามค้นหากรอบข้อความในรูปภาพและขณะนี้ใช้วิธีนี้:

// calculate the local variances of the grayscale image
Mat t_mean, t_mean_2;
Mat grayF;
outImg_gray.convertTo(grayF, CV_32F);
int winSize = 35;
blur(grayF, t_mean, cv::Size(winSize,winSize));
blur(grayF.mul(grayF), t_mean_2, cv::Size(winSize,winSize));
Mat varMat = t_mean_2 - t_mean.mul(t_mean);
varMat.convertTo(varMat, CV_8U);

// threshold the high variance regions
Mat varMatRegions = varMat > 100;

เมื่อได้รับภาพเช่นนี้:

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

จากนั้นเมื่อฉันแสดงvarMatRegionsฉันได้รับภาพนี้:

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

ในขณะที่คุณเห็นมันค่อนข้างรวมบล็อกด้านซ้ายของข้อความกับส่วนหัวของการ์ดสำหรับการ์ดส่วนใหญ่วิธีนี้ใช้งานได้ดี แต่บนการ์ดที่ยุ่งกว่าอาจทำให้เกิดปัญหาได้

เหตุผลที่มันไม่ดีสำหรับรูปทรงเหล่านั้นในการเชื่อมต่อคือมันทำให้กล่องขอบของเส้นขอบเกือบจะใช้การ์ดทั้งหมด

ใครช่วยแนะนำวิธีที่แตกต่างฉันสามารถค้นหาข้อความเพื่อให้แน่ใจว่าการตรวจสอบข้อความที่เหมาะสม?

200 คะแนนให้กับใครก็ได้ที่สามารถค้นหาข้อความในการ์ดเหนือสองคนนี้

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


1
วิธีที่ง่ายที่สุดที่ผมเห็นที่นี่จะเพิ่มความคมชัดก่อนที่จะได้รับการภูมิภาค ...
Paweł Stawarz

3
คำถามยอดเยี่ยม ขอบคุณสำหรับการโพสต์และโฮสต์รางวัลเพื่อให้แน่ใจว่าคำตอบที่น่าสนใจ
Geoff

ใหม่สำหรับการเขียนโปรแกรม สามารถทำสิ่งเดียวกันกับข้อความในสคริปต์นอกเหนือจากภาษาอังกฤษเช่นภาษาสันสกฤตได้หรือไม่?
Vamshi Krishna

คำตอบ:


127

คุณสามารถตรวจจับข้อความโดยการค้นหาองค์ประกอบที่ใกล้เคียง (ได้รับแรงบันดาลใจจาก LPD):

#include "opencv2/opencv.hpp"

std::vector<cv::Rect> detectLetters(cv::Mat img)
{
    std::vector<cv::Rect> boundRect;
    cv::Mat img_gray, img_sobel, img_threshold, element;
    cvtColor(img, img_gray, CV_BGR2GRAY);
    cv::Sobel(img_gray, img_sobel, CV_8U, 1, 0, 3, 1, 0, cv::BORDER_DEFAULT);
    cv::threshold(img_sobel, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
    element = getStructuringElement(cv::MORPH_RECT, cv::Size(17, 3) );
    cv::morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element); //Does the trick
    std::vector< std::vector< cv::Point> > contours;
    cv::findContours(img_threshold, contours, 0, 1); 
    std::vector<std::vector<cv::Point> > contours_poly( contours.size() );
    for( int i = 0; i < contours.size(); i++ )
        if (contours[i].size()>100)
        { 
            cv::approxPolyDP( cv::Mat(contours[i]), contours_poly[i], 3, true );
            cv::Rect appRect( boundingRect( cv::Mat(contours_poly[i]) ));
            if (appRect.width>appRect.height) 
                boundRect.push_back(appRect);
        }
    return boundRect;
}

การใช้งาน:

int main(int argc,char** argv)
{
    //Read
    cv::Mat img1=cv::imread("side_1.jpg");
    cv::Mat img2=cv::imread("side_2.jpg");
    //Detect
    std::vector<cv::Rect> letterBBoxes1=detectLetters(img1);
    std::vector<cv::Rect> letterBBoxes2=detectLetters(img2);
    //Display
    for(int i=0; i< letterBBoxes1.size(); i++)
        cv::rectangle(img1,letterBBoxes1[i],cv::Scalar(0,255,0),3,8,0);
    cv::imwrite( "imgOut1.jpg", img1);  
    for(int i=0; i< letterBBoxes2.size(); i++)
        cv::rectangle(img2,letterBBoxes2[i],cv::Scalar(0,255,0),3,8,0);
    cv::imwrite( "imgOut2.jpg", img2);  
    return 0;
}

ผล:

องค์ประกอบ = getStructuringElement (cv :: MORPH_RECT, cv :: ขนาด (17, 3)); imgOut1 imgOut2

ข องค์ประกอบ = getStructuringElement (cv :: MORPH_RECT, cv :: ขนาด (30, 30)); imgOut1 imgOut2

ผลลัพธ์คล้ายกันกับภาพอื่นที่กล่าวถึง


6
เครื่องตรวจจับป้ายทะเบียนรถยนต์
LovaBill

2
สำหรับการ์ดบางใบกล่องที่ล้อมรอบไม่ได้ล้อมรอบข้อความทั้งหมดเช่นตัวอักษรครึ่งตัวที่ถูกตัดออก เช่นการ์ดใบนี้: i.imgur.com/tX3XrwH.jpg ฉันจะขยายความสูงและความกว้างของกล่องขอบได้nอย่างไร ขอบคุณสำหรับการแก้ปัญหามันใช้งานได้ดี!
คลิป

4
cv::Rect a;พูด ขยายโดย a.x-=n/2;a.y-=n/2;a.width+=n;a.height+=n;n:
LovaBill

2
สวัสดีฉันจะบรรลุผลลัพธ์เดียวกันกับ python cv2 ได้อย่างไร
dnth

3
หนังสือ รหัส
LovaBill

128

ฉันใช้วิธีไล่ระดับสีตามโปรแกรมด้านล่าง เพิ่มภาพที่เกิด โปรดทราบว่าฉันกำลังใช้อิมเมจเวอร์ชันที่ลดขนาดเพื่อการประมวลผล

รุ่น c ++

The MIT License (MIT)

Copyright (c) 2014 Dhanushka Dangampola

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

#include "stdafx.h"

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

using namespace cv;
using namespace std;

#define INPUT_FILE              "1.jpg"
#define OUTPUT_FOLDER_PATH      string("")

int _tmain(int argc, _TCHAR* argv[])
{
    Mat large = imread(INPUT_FILE);
    Mat rgb;
    // downsample and use it for processing
    pyrDown(large, rgb);
    Mat small;
    cvtColor(rgb, small, CV_BGR2GRAY);
    // morphological gradient
    Mat grad;
    Mat morphKernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
    morphologyEx(small, grad, MORPH_GRADIENT, morphKernel);
    // binarize
    Mat bw;
    threshold(grad, bw, 0.0, 255.0, THRESH_BINARY | THRESH_OTSU);
    // connect horizontally oriented regions
    Mat connected;
    morphKernel = getStructuringElement(MORPH_RECT, Size(9, 1));
    morphologyEx(bw, connected, MORPH_CLOSE, morphKernel);
    // find contours
    Mat mask = Mat::zeros(bw.size(), CV_8UC1);
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(connected, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
    // filter contours
    for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
    {
        Rect rect = boundingRect(contours[idx]);
        Mat maskROI(mask, rect);
        maskROI = Scalar(0, 0, 0);
        // fill the contour
        drawContours(mask, contours, idx, Scalar(255, 255, 255), CV_FILLED);
        // ratio of non-zero pixels in the filled region
        double r = (double)countNonZero(maskROI)/(rect.width*rect.height);

        if (r > .45 /* assume at least 45% of the area is filled if it contains text */
            && 
            (rect.height > 8 && rect.width > 8) /* constraints on region size */
            /* these two conditions alone are not very robust. better to use something 
            like the number of significant peaks in a horizontal projection as a third condition */
            )
        {
            rectangle(rgb, rect, Scalar(0, 255, 0), 2);
        }
    }
    imwrite(OUTPUT_FOLDER_PATH + string("rgb.jpg"), rgb);

    return 0;
}

รุ่นหลาม

The MIT License (MIT)

Copyright (c) 2017 Dhanushka Dangampola

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

import cv2
import numpy as np

large = cv2.imread('1.jpg')
rgb = cv2.pyrDown(large)
small = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
grad = cv2.morphologyEx(small, cv2.MORPH_GRADIENT, kernel)

_, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
# using RETR_EXTERNAL instead of RETR_CCOMP
contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#For opencv 3+ comment the previous line and uncomment the following line
#_, contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

mask = np.zeros(bw.shape, dtype=np.uint8)

for idx in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[idx])
    mask[y:y+h, x:x+w] = 0
    cv2.drawContours(mask, contours, idx, (255, 255, 255), -1)
    r = float(cv2.countNonZero(mask[y:y+h, x:x+w])) / (w * h)

    if r > 0.45 and w > 8 and h > 8:
        cv2.rectangle(rgb, (x, y), (x+w-1, y+h-1), (0, 255, 0), 2)

cv2.imshow('rects', rgb)

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


3
ฉันเพิ่งดูวิธีการของเขา ความแตกต่างหลักที่ฉันเห็นคือเขาใช้ตัวกรอง Sobel ในขณะที่ฉันกำลังใช้ตัวกรองการไล่ระดับสีทางสัณฐานวิทยา ฉันคิดว่าฟิลเตอร์สัณฐานวิทยาและการสุ่มตัวอย่างแบนราบลงมาจากขอบที่ไม่แข็งแรงเท่าไหร่ Sobel อาจมีเสียงดังขึ้น
dhanushka

1
@ascenator เมื่อคุณรวม OTSU กับประเภทเกณฑ์มันจะใช้เกณฑ์ของ Otsu แทนค่าเกณฑ์ที่ระบุ ดูที่นี่
dhanushka

1
@VishnuJayanand rectคุณเพียงแค่ต้องใช้การปรับไป มีหนึ่งpyrdownดังนั้นคูณx, y, width, heightของrectโดย 4
Dhanushka

1
คุณช่วยให้เงื่อนไขที่สามแก่เราได้ไหม: จำนวนของจุดยอดที่สำคัญในการฉายในแนวนอนหรืออย่างน้อยก็นำ
ISlimani

2
@DotTye ใช้การฉายภาพในแนวนอนของรูปร่างที่เต็มไป (cv :: ลดลง) จากนั้นก็ขีด จำกัด (พูดโดยใช้ค่าเฉลี่ยหรือความสูงเฉลี่ย) หากคุณเห็นภาพผลลัพธ์นี้มันจะดูเหมือนบาร์โค้ด ฉันคิดว่าในขณะนั้นฉันกำลังคิดที่จะนับจำนวนแท่งและกำหนดเกณฑ์ให้กับมัน ตอนนี้ฉันคิดว่าถ้าภูมิภาคสะอาดเพียงพออาจช่วยได้ถ้าเราสามารถป้อนเข้า OCR และรับระดับความมั่นใจสำหรับอักขระที่ตรวจพบแต่ละตัวเพื่อให้แน่ใจว่าพื้นที่นั้นมีข้อความ
dhanushka

51

นี่เป็นวิธีทางเลือกที่ฉันใช้ในการตรวจจับบล็อคข้อความ:

  1. แปลงภาพเป็นโทนสีเทา
  2. เกณฑ์ที่ใช้แล้ว( ขีด จำกัดไบนารีแบบง่ายที่มีค่าที่คัดเลือก 150 เป็นค่าขีด จำกัด )
  3. ใช้การขยายเพื่อเพิ่มความหนาของเส้นภาพทำให้วัตถุมีขนาดเล็กลงและมีพื้นที่ว่างน้อยลง ใช้ค่าสูงสำหรับจำนวนการวนซ้ำดังนั้นการขยายจึงหนักมาก (การวนซ้ำซ้ำ 13 ครั้งยังได้รับการคัดเลือกเพื่อผลลัพธ์ที่ดีที่สุด)
  4. ระบุรูปทรงของวัตถุในภาพผลลัพธ์โดยใช้ฟังก์ชั่นopencv findContours
  5. ดึงกล่องที่มีขอบเขต (สี่เหลี่ยม) ล้อมรอบวัตถุแต่ละอันที่มีเส้นโค้ง
  6. พื้นที่ที่ถูกทิ้งทางเลือกซึ่งไม่น่าจะเป็นวัตถุที่คุณกำลังค้นหา (เช่นบล็อคข้อความ) ตามขนาดเนื่องจากอัลกอริทึมด้านบนยังสามารถค้นหาวัตถุที่ตัดกันหรือซ้อนกัน (เช่นพื้นที่ด้านบนทั้งหมดสำหรับบัตรใบแรก) ซึ่งอาจเป็น ไม่น่าสนใจสำหรับวัตถุประสงค์ของคุณ

ด้านล่างเป็นรหัสที่เขียนด้วยภาษาไพ ธ อนด้วย pyopencv ควรง่ายต่อการพอร์ตไปยัง C ++

import cv2

image = cv2.imread("card.png")
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # grayscale
_,thresh = cv2.threshold(gray,150,255,cv2.THRESH_BINARY_INV) # threshold
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
dilated = cv2.dilate(thresh,kernel,iterations = 13) # dilate
_, contours, hierarchy = cv2.findContours(dilated,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) # get contours

# for each contour found, draw a rectangle around it on original image
for contour in contours:
    # get rectangle bounding contour
    [x,y,w,h] = cv2.boundingRect(contour)

    # discard areas that are too large
    if h>300 and w>300:
        continue

    # discard areas that are too small
    if h<40 or w<40:
        continue

    # draw rectangle around contour on original image
    cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,255),2)

# write original image with added contours to disk  
cv2.imwrite("contoured.jpg", image) 

ภาพต้นฉบับเป็นภาพแรกในโพสต์ของคุณ

หลังจากการประมวลผลล่วงหน้า (โทนสีเทา, ขีด จำกัด และขยาย - ดังนั้นหลังจากขั้นตอนที่ 3) ภาพจะมีลักษณะดังนี้:

รูปภาพที่ขยาย

ด้านล่างนี้เป็นภาพผลลัพธ์ ("contoured.jpg" ในบรรทัดสุดท้าย); กล่อง bounding สุดท้ายสำหรับวัตถุในภาพมีลักษณะดังนี้:

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

คุณสามารถเห็นบล็อกข้อความทางด้านซ้ายตรวจพบว่าเป็นบล็อกแยกคั่นด้วยสภาพแวดล้อม

ใช้สคริปต์เดียวกันกับพารามิเตอร์เดียวกัน (ยกเว้นประเภท thresholding ที่เปลี่ยนสำหรับภาพที่สองตามที่อธิบายไว้ด้านล่าง) นี่คือผลลัพธ์สำหรับการ์ด 2 ใบอื่น ๆ :

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

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

ปรับพารามิเตอร์

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

สำหรับการกำหนดเกณฑ์ขั้นต่ำ (ขั้นตอนที่ 2) ฉันใช้เกณฑ์ดำ สำหรับรูปภาพที่ข้อความเบากว่าพื้นหลังเช่นรูปภาพที่สองในโพสต์ของคุณควรใช้เกณฑ์สีขาวเพื่อแทนที่ประเภทการแชร์ด้วยcv2.THRESH_BINARY) สำหรับภาพที่สองฉันยังใช้ค่าที่สูงขึ้นเล็กน้อยสำหรับเกณฑ์ (180) การเปลี่ยนแปลงพารามิเตอร์สำหรับค่าขีด จำกัด และจำนวนการวนซ้ำสำหรับการขยายจะทำให้ระดับความไวที่แตกต่างกันในการกำหนดวัตถุในภาพ

ค้นหาประเภทวัตถุอื่น ๆ :

ตัวอย่างเช่นการลดการขยายออกเป็น 5 การวนซ้ำในรูปภาพแรกจะทำให้เรามีการแบ่งวัตถุในภาพได้ละเอียดมากขึ้นโดยการค้นหาคำทั้งหมดในภาพโดยประมาณ(แทนที่จะเป็นบล็อคข้อความ):

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

รู้ขนาดคร่าวๆของที่นี่ฉันทิ้งพื้นที่ที่มีขนาดเล็กเกินไป (ต่ำกว่า 20 พิกเซลกว้างหรือสูง) หรือใหญ่เกินไป (สูงกว่า 100 พิกเซลกว้างหรือสูง) เพื่อละเว้นวัตถุที่ไม่น่าจะเป็นคำเพื่อให้ได้ผลลัพธ์ใน ภาพด้านบน


2
คุณช่างน่าอัศจรรย์! ฉันจะลองในตอนเช้า
คลิป

ฉันได้เพิ่มอีกขั้นตอนหนึ่งสำหรับการทิ้งวัตถุที่ไม่น่าสนใจ นอกจากนี้ยังเพิ่มตัวอย่างเช่นการระบุคำหรือชนิดอื่น ๆ ของวัตถุ (กว่าบล็อกของข้อความ)
Anana

ขอบคุณสำหรับคำตอบรายละเอียด cv2.findContoursแต่ฉันได้รับข้อผิดพลาดใน ValueError: too many values to unpackมันบอกว่า
Abhijith

1
ปัญหาคือฟังก์ชั่นcv2.findContoursส่งกลับ 3 ข้อโต้แย้งและรหัสเดิมจับเพียง 2
Abhijith

@Abhijith cv2 ในรุ่นที่สองกลับมาสอง args แต่ตอนนี้ในรุ่นที่สามจะส่งคืน 3
Tomasz Giba

27

@ แนวทางของ dhanushka แสดงให้เห็นถึงคำสัญญาที่ดีที่สุด แต่ฉันต้องการที่จะเล่นใน Python ดังนั้นจึงไปข้างหน้าและแปลมันเพื่อความสนุกสนาน:

import cv2
import numpy as np
from cv2 import boundingRect, countNonZero, cvtColor, drawContours, findContours, getStructuringElement, imread, morphologyEx, pyrDown, rectangle, threshold

large = imread(image_path)
# downsample and use it for processing
rgb = pyrDown(large)
# apply grayscale
small = cvtColor(rgb, cv2.COLOR_BGR2GRAY)
# morphological gradient
morph_kernel = getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
grad = morphologyEx(small, cv2.MORPH_GRADIENT, morph_kernel)
# binarize
_, bw = threshold(src=grad, thresh=0, maxval=255, type=cv2.THRESH_BINARY+cv2.THRESH_OTSU)
morph_kernel = getStructuringElement(cv2.MORPH_RECT, (9, 1))
# connect horizontally oriented regions
connected = morphologyEx(bw, cv2.MORPH_CLOSE, morph_kernel)
mask = np.zeros(bw.shape, np.uint8)
# find contours
im2, contours, hierarchy = findContours(connected, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# filter contours
for idx in range(0, len(hierarchy[0])):
    rect = x, y, rect_width, rect_height = boundingRect(contours[idx])
    # fill the contour
    mask = drawContours(mask, contours, idx, (255, 255, 2555), cv2.FILLED)
    # ratio of non-zero pixels in the filled region
    r = float(countNonZero(mask)) / (rect_width * rect_height)
    if r > 0.45 and rect_height > 8 and rect_width > 8:
        rgb = rectangle(rgb, (x, y+rect_height), (x+rect_width, y), (0,255,0),3)

ตอนนี้เพื่อแสดงภาพ:

from PIL import Image
Image.fromarray(rgb).show()

ไม่ใช่ Pythonic ส่วนใหญ่ของสคริปต์ แต่ฉันพยายามคล้ายกับรหัส C ++ ดั้งเดิมมากที่สุดเท่าที่จะเป็นไปได้เพื่อให้ผู้อ่านติดตาม

มันทำงานได้เกือบเหมือนต้นฉบับ ฉันยินดีที่จะอ่านคำแนะนำว่าจะปรับปรุง / แก้ไขอย่างไรให้คล้ายกับผลลัพธ์ต้นฉบับได้อย่างเต็มที่

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

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

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


3
ขอบคุณสำหรับการให้บริการรุ่นหลาม หลายคนจะพบว่ามีประโยชน์นี้ +1
dhanushka

ความแตกต่างระหว่างการกรอกเส้นชั้นความสูงและการวาดคืออะไร? ฉันพบรหัสโดยไม่มีขั้นตอนการกรอกข้อมูลที่นี่: stackoverflow.com/a/23556997/6837132
SarahData

@SarahM ฉันไม่ทราบว่าคุณกำลังถามเกี่ยวกับความแตกต่างทั่วไประหว่างการวาดภาพและการเติม (ฉันคิดว่าค่อนข้างชัดเจน?) หรือ OpenCV API โดยเฉพาะ? หากหลังดูเอกสารสำหรับdrawContoursรัฐนั้น "ฟังก์ชั่นวาดเส้นโครงร่างในภาพหากความหนา> 0 หรือเติมเต็มพื้นที่ที่ล้อมรอบด้วยรูปทรงถ้าความหนา <0" เสร็จแล้วเราสามารถตรวจสอบอัตราส่วนของพิกเซลที่ไม่เป็นศูนย์เพื่อตัดสินใจว่ากล่องนั้นมีข้อความหรือไม่
rtkaleta

15

คุณสามารถลองวิธีนี้ที่พัฒนาโดย Chucai Yi และ Yingli Tian

พวกเขายังแชร์ซอฟต์แวร์ (ซึ่งใช้ Opencv-1.0 และควรทำงานภายใต้แพลตฟอร์ม Windows) ซึ่งคุณสามารถใช้ (แม้ว่าจะไม่มีซอร์สโค้ด) มันจะสร้างกล่องขอบข้อความทั้งหมด (แสดงในเงาสี) ในภาพ เมื่อนำไปใช้กับภาพตัวอย่างของคุณคุณจะได้รับผลลัพธ์ต่อไปนี้:

หมายเหตุ: เพื่อให้ผลลัพธ์มีประสิทธิภาพมากขึ้นคุณสามารถรวมกล่องที่อยู่ติดกันเข้าด้วยกัน


อัปเดต:หากเป้าหมายสูงสุดของคุณคือการจดจำข้อความในภาพคุณสามารถตรวจสอบgttextซึ่งเป็นซอฟต์แวร์ฟรี OCR และเครื่องมือกราวด์รูทติ้งสำหรับภาพสีพร้อมข้อความ ซอร์สโค้ดก็มีให้เช่นกัน

ด้วยสิ่งนี้คุณจะได้รับข้อความที่เป็นที่รู้จักเช่น:


gttext สำหรับ windows ข้อเสนอแนะใด ๆ สำหรับผู้ใช้ Mac / Linux
Saghir A. Khatri

5

เหนือโค้ดรุ่น JAVA: ขอบคุณ @William

public static List<Rect> detectLetters(Mat img){    
    List<Rect> boundRect=new ArrayList<>();

    Mat img_gray =new Mat(), img_sobel=new Mat(), img_threshold=new Mat(), element=new Mat();
    Imgproc.cvtColor(img, img_gray, Imgproc.COLOR_RGB2GRAY);
    Imgproc.Sobel(img_gray, img_sobel, CvType.CV_8U, 1, 0, 3, 1, 0, Core.BORDER_DEFAULT);
    //at src, Mat dst, double thresh, double maxval, int type
    Imgproc.threshold(img_sobel, img_threshold, 0, 255, 8);
    element=Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(15,5));
    Imgproc.morphologyEx(img_threshold, img_threshold, Imgproc.MORPH_CLOSE, element);
    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(img_threshold, contours,hierarchy, 0, 1);

    List<MatOfPoint> contours_poly = new ArrayList<MatOfPoint>(contours.size());

     for( int i = 0; i < contours.size(); i++ ){             

         MatOfPoint2f  mMOP2f1=new MatOfPoint2f();
         MatOfPoint2f  mMOP2f2=new MatOfPoint2f();

         contours.get(i).convertTo(mMOP2f1, CvType.CV_32FC2);
         Imgproc.approxPolyDP(mMOP2f1, mMOP2f2, 2, true); 
         mMOP2f2.convertTo(contours.get(i), CvType.CV_32S);


            Rect appRect = Imgproc.boundingRect(contours.get(i));
            if (appRect.width>appRect.height) {
                boundRect.add(appRect);
            }
     }

    return boundRect;
}

และใช้รหัสนี้ในทางปฏิบัติ:

        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        Mat img1=Imgcodecs.imread("abc.png");
        List<Rect> letterBBoxes1=Utils.detectLetters(img1);

        for(int i=0; i< letterBBoxes1.size(); i++)
            Imgproc.rectangle(img1,letterBBoxes1.get(i).br(), letterBBoxes1.get(i).tl(),new Scalar(0,255,0),3,8,0);         
        Imgcodecs.imwrite("abc1.png", img1);

2

Python Implementation สำหรับโซลูชันของ @ dhanushka:

def process_rgb(rgb):
    hasText = False
    gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
    morphKernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
    grad = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, morphKernel)
    # binarize
    _, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    # connect horizontally oriented regions
    morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
    connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, morphKernel)
    # find contours
    mask = np.zeros(bw.shape[:2], dtype="uint8")
    _,contours, hierarchy = cv2.findContours(connected, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    # filter contours
    idx = 0
    while idx >= 0:
        x,y,w,h = cv2.boundingRect(contours[idx])
        # fill the contour
        cv2.drawContours(mask, contours, idx, (255, 255, 255), cv2.FILLED)
        # ratio of non-zero pixels in the filled region
        r = cv2.contourArea(contours[idx])/(w*h)
        if(r > 0.45 and h > 5 and w > 5 and w > h):
            cv2.rectangle(rgb, (x,y), (x+w,y+h), (0, 255, 0), 2)
            hasText = True
        idx = hierarchy[0][idx][0]
    return hasText, rgb

ทำไมจึงใช้หน้ากาก
SarahData

1
คำตอบที่ซ้ำกัน ก็จะได้รับประโยชน์มากขึ้นถ้าคุณมีส่วนร่วมในการสนทนาบนstackoverflow.com/a/43283990/6809909
rtkaleta

2

นี่คือคำตอบจาก dhanushka รุ่น C # โดยใช้OpenCVSharp

        Mat large = new Mat(INPUT_FILE);
        Mat rgb = new Mat(), small = new Mat(), grad = new Mat(), bw = new Mat(), connected = new Mat();

        // downsample and use it for processing
        Cv2.PyrDown(large, rgb);
        Cv2.CvtColor(rgb, small, ColorConversionCodes.BGR2GRAY);

        // morphological gradient
        var morphKernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new OpenCvSharp.Size(3, 3));
        Cv2.MorphologyEx(small, grad, MorphTypes.Gradient, morphKernel);

        // binarize
        Cv2.Threshold(grad, bw, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);

        // connect horizontally oriented regions
        morphKernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(9, 1));
        Cv2.MorphologyEx(bw, connected, MorphTypes.Close, morphKernel);

        // find contours
        var mask = new Mat(Mat.Zeros(bw.Size(), MatType.CV_8UC1), Range.All);
        Cv2.FindContours(connected, out OpenCvSharp.Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.CComp, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(0, 0));

        // filter contours
        var idx = 0;
        foreach (var hierarchyItem in hierarchy)
        {
            idx = hierarchyItem.Next;
            if (idx < 0)
                break;
            OpenCvSharp.Rect rect = Cv2.BoundingRect(contours[idx]);
            var maskROI = new Mat(mask, rect);
            maskROI.SetTo(new Scalar(0, 0, 0));

            // fill the contour
            Cv2.DrawContours(mask, contours, idx, Scalar.White, -1);

            // ratio of non-zero pixels in the filled region
            double r = (double)Cv2.CountNonZero(maskROI) / (rect.Width * rect.Height);
            if (r > .45 /* assume at least 45% of the area is filled if it contains text */
                 &&
            (rect.Height > 8 && rect.Width > 8) /* constraints on region size */
            /* these two conditions alone are not very robust. better to use something 
            like the number of significant peaks in a horizontal projection as a third condition */
            )
            {
                Cv2.Rectangle(rgb, rect, new Scalar(0, 255, 0), 2);
            }
        }

        rgb.SaveImage(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rgb.jpg"));

0

นี้เป็นรุ่น VB.NET ของคำตอบจาก Dhanushka ใช้EmguCV

ฟังก์ชั่นและโครงสร้างบางอย่างใน EmguCV ต้องการการพิจารณาที่แตกต่างจากเวอร์ชั่น C # ด้วย OpenCVSharp

Imports Emgu.CV
Imports Emgu.CV.Structure
Imports Emgu.CV.CvEnum
Imports Emgu.CV.Util

        Dim input_file As String = "C:\your_input_image.png"
        Dim large As Mat = New Mat(input_file)
        Dim rgb As New Mat
        Dim small As New Mat
        Dim grad As New Mat
        Dim bw As New Mat
        Dim connected As New Mat
        Dim morphanchor As New Point(0, 0)

        '//downsample and use it for processing
        CvInvoke.PyrDown(large, rgb)
        CvInvoke.CvtColor(rgb, small, ColorConversion.Bgr2Gray)

        '//morphological gradient
        Dim morphKernel As Mat = CvInvoke.GetStructuringElement(ElementShape.Ellipse, New Size(3, 3), morphanchor)
        CvInvoke.MorphologyEx(small, grad, MorphOp.Gradient, morphKernel, New Point(0, 0), 1, BorderType.Isolated, New MCvScalar(0))

        '// binarize
        CvInvoke.Threshold(grad, bw, 0, 255, ThresholdType.Binary Or ThresholdType.Otsu)

        '// connect horizontally oriented regions
        morphKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, New Size(9, 1), morphanchor)
        CvInvoke.MorphologyEx(bw, connected, MorphOp.Close, morphKernel, morphanchor, 1, BorderType.Isolated, New MCvScalar(0))

        '// find contours
        Dim mask As Mat = Mat.Zeros(bw.Size.Height, bw.Size.Width, DepthType.Cv8U, 1)  '' MatType.CV_8UC1
        Dim contours As New VectorOfVectorOfPoint
        Dim hierarchy As New Mat

        CvInvoke.FindContours(connected, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple, Nothing)

        '// filter contours
        Dim idx As Integer
        Dim rect As Rectangle
        Dim maskROI As Mat
        Dim r As Double
        For Each hierarchyItem In hierarchy.GetData
            rect = CvInvoke.BoundingRectangle(contours(idx))
            maskROI = New Mat(mask, rect)
            maskROI.SetTo(New MCvScalar(0, 0, 0))

            '// fill the contour
            CvInvoke.DrawContours(mask, contours, idx, New MCvScalar(255), -1)

            '// ratio of non-zero pixels in the filled region
            r = CvInvoke.CountNonZero(maskROI) / (rect.Width * rect.Height)

            '/* assume at least 45% of the area Is filled if it contains text */
            '/* constraints on region size */
            '/* these two conditions alone are Not very robust. better to use something 
            'Like the number of significant peaks in a horizontal projection as a third condition */
            If r > 0.45 AndAlso rect.Height > 8 AndAlso rect.Width > 8 Then
                'draw green rectangle
                CvInvoke.Rectangle(rgb, rect, New MCvScalar(0, 255, 0), 2)
            End If
            idx += 1
        Next
        rgb.Save(IO.Path.Combine(Application.StartupPath, "rgb.jpg"))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.