OpenCV2でしきい値を正方形に分割するにはどうすればよいですか?

アレクサンダークラッグス

素敵なルービックキューブの写真があります。

ルービックキューブ

それを正方形に分割して、各正方形の色を識別したいと思います。ガウスぼかしを実行し、続いて「キャニー」を実行してから「拡張」で終了すると、次のようになります。

膨張後

これは見た目は良さそうですが、正方形に変えることはできません。私が試した「findContours」の種類は、1つまたは2つの正方形しか表示しません。私が目指している9つの近くにはどこにもありません。私がこれを超えて何ができるかについて、人々は何か考えを持っていますか?

現在の最良の解決策:

側面

コードは以下のとおりで、numpy + opencv2が必要です。'./sides/rubiks-side-F.png'というファイルが必要で、いくつかのファイルを 'steps'フォルダーに出力します。

import numpy as np
import cv2 as cv

def save_image(name, file):
    return cv.imwrite('./steps/' + name + '.png', file)


def angle_cos(p0, p1, p2):
    d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float')
    return abs(np.dot(d1, d2) / np.sqrt(np.dot(d1, d1)*np.dot(d2, d2)))

def find_squares(img):
    img = cv.GaussianBlur(img, (5, 5), 0)
    squares = []
    for gray in cv.split(img):
        bin = cv.Canny(gray, 500, 700, apertureSize=5)
        save_image('post_canny', bin)
        bin = cv.dilate(bin, None)
        save_image('post_dilation', bin)
        for thrs in range(0, 255, 26):
            if thrs != 0:
                _retval, bin = cv.threshold(gray, thrs, 255, cv.THRESH_BINARY)
                save_image('threshold', bin)
            contours, _hierarchy = cv.findContours(
                bin, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
            for cnt in contours:
                cnt_len = cv.arcLength(cnt, True)
                cnt = cv.approxPolyDP(cnt, 0.02*cnt_len, True)
                if len(cnt) == 4 and cv.contourArea(cnt) > 1000 and cv.isContourConvex(cnt):
                    cnt = cnt.reshape(-1, 2)
                    max_cos = np.max(
                        [angle_cos(cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4]) for i in range(4)])
                    if max_cos < 0.2:
                        squares.append(cnt)
    return squares

img = cv.imread("./sides/rubiks-side-F.png")
squares = find_squares(img)
cv.drawContours(img, squares, -1, (0, 255, 0), 3)
save_image('squares', img)

You can find other sides here

eldesgraciado

I know that you might not accept this answer because it is written in C++. That's ok; I just want to show you a possible approach for detecting the squares. I'll try to include as much detail as possible if you wish to port this code to Python.

The goal is to detect all 9 squares, as accurately as possible. These are the steps:

  1. Get an edge mask where the outline of the complete cube is clear and visible.
  2. Filter these edges to get a binary cube (segmentation) mask.
  3. Use the cube mask to get the cube’s bounding box/rectangle.
  4. Use the bounding rectangle to get the dimensions and location of each square (all the squares have constant dimensions).

First, I'll try to get an edges mask applying the steps you described. I just want to make sure I get to a similar starting point like where you currently are.

The pipeline is this: read the image > grayscale conversion > Gaussian Blur > Canny Edge detector:

    //read the input image:
    std::string imageName = "C://opencvImages//cube.png";
    cv::Mat testImage =  cv::imread( imageName );

    //Convert BGR to Gray:
    cv::Mat grayImage;
    cv::cvtColor( testImage, grayImage, cv::COLOR_RGB2GRAY );

   //Apply Gaussian blur with a X-Y Sigma of 50:
    cv::GaussianBlur( grayImage, grayImage, cv::Size(3,3), 50, 50 );

    //Prepare edges matrix:
    cv::Mat testEdges;

    //Setup lower and upper thresholds for edge detection:
    float lowerThreshold = 20;
    float upperThreshold = 3 * lowerThreshold;

    //Get Edges via Canny:
    cv::Canny( grayImage, testEdges, lowerThreshold, upperThreshold );

Alright, this is the starting point. This is the edges mask I get:

Close to your results. Now, I'll apply a dilation. Here, the number of iterations of the operation is important, because I want nice, thick edges. Closing opened contours is also desired, so, I want an mild-aggressive dilation. I set the number of iterations = 5 using a rectangular structuring element.

    //Prepare a rectangular, 3x3 structuring element:
    cv::Mat SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3, 3) );

    //OP iterations:
    int dilateIterations = 5;

   //Prepare the dilation matrix:
    cv::Mat binDilation;

   //Perform the morph operation:
    cv::morphologyEx( testEdges, binDilation, cv::MORPH_DILATE, SE, cv::Point(-1,-1), dilateIterations );

I get this:

This is the output so far with nice and very defined edges. The most important thing is to clearly define the cube, because I'll rely on its outline to compute the bounding rectangle later.

What follows is my attempt to clean the cube's edges from everything else as accurately as possible. There's a lot of garbage and pixels that do not belong to the cube, as you can see. I'm especially interested on flood-filling the background with a color (white) different from the cube (black) in order to get a nice segmentation.

Flood-filling has a disadvantage, though. It can also fill the interior of a contour if it is not closed. I try to clean garbage and close contours in one go with a "border mask", which are just white lines at the side of the dilation mask.

I implement this mask as four SUPER THICK lines that border the dilation mask. To apply the lines I need starting and ending points, which correspond to the image corners. These are defined in a vector:

    std::vector< std::vector<cv::Point> > imageCorners;
    imageCorners.push_back( { cv::Point(0,0), cv::Point(binDilation.cols,0) } );
    imageCorners.push_back( { cv::Point(binDilation.cols,0), cv::Point(binDilation.cols, binDilation.rows) } );
    imageCorners.push_back( { cv::Point(binDilation.cols, binDilation.rows), cv::Point(0,binDilation.rows) } );
    imageCorners.push_back( { cv::Point(0,binDilation.rows), cv::Point(0, 0) } );

Four starting/ending coordinates in a vector of four entries. I apply the "border mask" looping through these coordinates and drawing the thick lines:

    //Define the SUPER THICKNESS:
    int lineThicness  = 200;

    //Loop through my line coordinates and draw four lines at the borders:
    for ( int c = 0 ; c < 4 ; c++ ){
        //Get current vector of points:
        std::vector<cv::Point> currentVect = imageCorners[c];
       //Get the starting/ending points:
        cv::Point startPoint = currentVect[0];
        cv::Point endPoint = currentVect[1];
        //Draw the line:
        cv::line( binDilation, startPoint, endPoint, cv::Scalar(255,255,255), lineThicness );
    }

Cool. This gets me this output:

Now, let's apply the floodFill algorithm. This operation will fill a closed area of same colored pixels with a "substitute" color. It needs a seed point and the substitute color (white in this case). Let's Flood-fill at the four corners inside of the white mask we just created.

    //Set the offset of the image corners. Ensure the area to be filled is black:
    int fillOffsetX = 200;
    int fillOffsetY = 200;
    cv::Scalar fillTolerance = 0; //No tolerance
    int fillColor = 255; //Fill color is white
   
    //Get the dimensions of the image:
    int targetCols = binDilation.cols;
    int targetRows = binDilation.rows;

    //Flood-fill at the four corners of the image:
    cv::floodFill( binDilation, cv::Point( fillOffsetX, fillOffsetY ), fillColor, (cv::Rect*)0, fillTolerance, fillTolerance);
    cv::floodFill( binDilation, cv::Point( fillOffsetX, targetRows - fillOffsetY ), fillColor, (cv::Rect*)0, fillTolerance, fillTolerance);
    cv::floodFill( binDilation, cv::Point( targetCols - fillOffsetX, fillOffsetY ), fillColor, (cv::Rect*)0, fillTolerance, fillTolerance);
    cv::floodFill( binDilation, cv::Point( targetCols - fillOffsetX, targetRows - fillOffsetY ), fillColor, (cv::Rect*)0, fillTolerance, fillTolerance);

This can also be implemented as a loop, just like the "border mask". After this operation I get this mask:

Getting close, right? Now, depending on your image, some garbage could survive all these "cleaning" operations. I'd suggest applying an area filter. The area filter will remove every blob of pixels that is under a threshold area. This is useful, because the cube's blobs are the biggest blobs on the mask and those surely will survive the area filter.

Anyway, I'm just interested on the cube's outline; I don't need those lines inside the cube. I'm going to dilate the hell out of the (inverted) blob and then erode back to original dimensions to get rid of the lines inside the cube:

    //Get the inverted image:
    cv::Mat cubeMask = 255 - binDilation;

    //Set some really high iterations here:
    int closeIterations = 50;

    //Dilate
    cv::morphologyEx( cubeMask, cubeMask, cv::MORPH_DILATE, SE, cv::Point(-1,-1), closeIterations );
    //Erode:
    cv::morphologyEx( cubeMask, cubeMask, cv::MORPH_ERODE, SE, cv::Point(-1,-1), closeIterations );

This is a closing operation. And a pretty brutal one, this is the result of applying it. Remember I previously inverted the image:

Isn't that nice or what? Check out the cube mask, here overlaid into the original RBG image:

Excellent, now let's get the bounding box of this blob. The approach is as follows:

Get blob contour > Convert contour to bounding box

This is fairly straightforward to implement, and the Python equivalent should be very similar to this. First, get the contours via findContours. As you see, there should be only one contour: the cube outline. Next, convert the contour to a bounding rectangle using boundingRect. In C++ this is the code:

    //Lets get the blob contour:
    std::vector< std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;

    cv::findContours( cubeMask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

    //There should be only one contour, the item number 0:
    cv::Rect boundigRect = cv::boundingRect( contours[0] );

These are the contours found (just one):

Once you convert this contour to a bounding rectangle, you can get this nice image:

Ah, we are very close to the end here. As all the squares have the same dimensions and your image doesn’t seem to be very perspective-distorted, we can use the bounding rectangle to estimate the square dimensions. All the squares have the same width and height, there are 3 squares per cube width and 3 squares per cube height.

Divide the bounding rectangle in 9 equal sub-squares (or, as I call them, "grids") and get their dimensions and location starting from the coordinates of the bounding box, like this:

    //Number of squares or "grids"
    int verticalGrids = 3;
    int horizontalGrids = 3;

    //Grid dimensions:
    float gridWidth = (float)boundigRect.width / 3.0;
    float gridHeight = (float)boundigRect.height / 3.0;

    //Grid counter:
    int gridCounter = 1;
    
    //Loop thru vertical dimension:
    for ( int j = 0; j < verticalGrids; ++j ) {

        //Grid starting Y:
        int yo = j * gridHeight;

        //Loop thru horizontal dimension:
        for ( int i = 0; i < horizontalGrids; ++i ) {

            //Grid starting X:
            int xo = i * gridWidth;
            
            //Grid dimensions:
            cv::Rect gridBox;
            gridBox.x = boundigRect.x + xo;
            gridBox.y = boundigRect.y + yo;
            gridBox.width = gridWidth;
            gridBox.height = gridHeight;

            //Draw a rectangle using the grid dimensions:
            cv::rectangle( testImage, gridBox, cv::Scalar(0,0,255), 5 );

            //Int to string:
            std::string gridCounterString = std::to_string( gridCounter );

            //String position:
            cv::Point textPosition;
            textPosition.x = gridBox.x + 0.5 * gridBox.width;
            textPosition.y = gridBox.y + 0.5 * gridBox.height;

            //Draw string:
            cv::putText( testImage, gridCounterString, textPosition, cv::FONT_HERSHEY_SIMPLEX,
                         1, cv::Scalar(255,0,0), 3, cv::LINE_8, false );

            gridCounter++;

        }

    }

Here, for each grid, I'm drawing its rectangle and a nice number at the center of it. The draw rectangle function requires a defined rectangle: Upper left starting coordinates and rectangle width and height, which are defined using the gridBox variable of cv::Rect type.

Here's a cool animation of how the cube gets divided into 9 grids:

Here’s the final image!

Some suggestions:

  1. Your source image is way too big, try resizing it to a smaller size, operate
    on it and scale back the results.
  2. Implement the area filter. It is very handy in getting rid of small blobs of pixels.
  3. 画像(質問に投稿した後でテストしたばかりです)とカメラによって導入された遠近法の歪みによっては、単純なcontourものでboundingRectは不十分な場合があります。その場合、別のアプローチは、ハフ線検出を介して立方体の輪郭の4つのポイントを取得することです。

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

CSSで正方形の正方形を取得するにはどうすればよいですか?

分類Dev

この正方形が特定の値に達した後、この正方形の方向を逆にするにはどうすればよいですか?

分類Dev

Flutterで正方形の隆起したボタンを作成するにはどうすればよいですか?

分類Dev

OpenCVとPythonを使用して長方形の画像を正方形にするにはどうすればよいですか?

分類Dev

長方形の写真を正方形の写真に入れるにはどうすればよいですか?

分類Dev

Webページを正方形として表示するにはどうすればよいですか?

分類Dev

色で正方形にするボタンを作成するにはどうすればよいですか?

分類Dev

正方形を互いに同じレベルにするにはどうすればよいですか?

分類Dev

Xamarinで子供に正方形の寸法を強制するにはどうすればよいですか?

分類Dev

pygameで正方形と円を同時に表示するにはどうすればよいですか?

分類Dev

AndroidでimageViewを正方形ビューに設定するにはどうすればよいですか?

分類Dev

matplotlib散布図を正方形にするにはどうすればよいですか?

分類Dev

AbsoluteLayoutに正方形のレイアウトを強制するにはどうすればよいですか?

分類Dev

この正方形のグリッドを中央に配置するにはどうすればよいですか?

分類Dev

正方形を目的地に移動するにはどうすればよいですか?

分類Dev

matplotlibで正方形グリッドを適用するにはどうすればよいですか

分類Dev

Pythonで整数の正方形のリストを作成するにはどうすればよいですか?

分類Dev

cssで不規則な正方形を作成するにはどうすればよいですか?

分類Dev

CSSでテーブルの背景の正方形を削除するにはどうすればよいですか?

分類Dev

ncursesで正方形のシンボルを設計するにはどうすればよいですか?

分類Dev

Qt QMLで正方形のボタンを作成するにはどうすればよいですか?

分類Dev

ポイントの周りに正方形を合わせるにはどうすればよいですか?

分類Dev

カメを正方形の特定の領域に重ねるにはどうすればよいですか?

分類Dev

この正方形を壁に跳ね返らせるにはどうすればよいですか?

分類Dev

CSS-2つの正方形の中央に長方形を配置するにはどうすればよいですか

分類Dev

Opencv:4つの台形画像をつなぎ合わせて正方形の画像を作成するにはどうすればよいですか?

分類Dev

「正方形」の2次元配列をマッピングして座標を切り替えるにはどうすればよいですか?(ルビー)

分類Dev

ブートストラップの2つの列の中央に正方形のdivを中央に配置するにはどうすればよいですか?

分類Dev

ページ内に正方形(幅と高さの約80%、色付き)を追加するにはどうすればよいですか?

Related 関連記事

  1. 1

    CSSで正方形の正方形を取得するにはどうすればよいですか?

  2. 2

    この正方形が特定の値に達した後、この正方形の方向を逆にするにはどうすればよいですか?

  3. 3

    Flutterで正方形の隆起したボタンを作成するにはどうすればよいですか?

  4. 4

    OpenCVとPythonを使用して長方形の画像を正方形にするにはどうすればよいですか?

  5. 5

    長方形の写真を正方形の写真に入れるにはどうすればよいですか?

  6. 6

    Webページを正方形として表示するにはどうすればよいですか?

  7. 7

    色で正方形にするボタンを作成するにはどうすればよいですか?

  8. 8

    正方形を互いに同じレベルにするにはどうすればよいですか?

  9. 9

    Xamarinで子供に正方形の寸法を強制するにはどうすればよいですか?

  10. 10

    pygameで正方形と円を同時に表示するにはどうすればよいですか?

  11. 11

    AndroidでimageViewを正方形ビューに設定するにはどうすればよいですか?

  12. 12

    matplotlib散布図を正方形にするにはどうすればよいですか?

  13. 13

    AbsoluteLayoutに正方形のレイアウトを強制するにはどうすればよいですか?

  14. 14

    この正方形のグリッドを中央に配置するにはどうすればよいですか?

  15. 15

    正方形を目的地に移動するにはどうすればよいですか?

  16. 16

    matplotlibで正方形グリッドを適用するにはどうすればよいですか

  17. 17

    Pythonで整数の正方形のリストを作成するにはどうすればよいですか?

  18. 18

    cssで不規則な正方形を作成するにはどうすればよいですか?

  19. 19

    CSSでテーブルの背景の正方形を削除するにはどうすればよいですか?

  20. 20

    ncursesで正方形のシンボルを設計するにはどうすればよいですか?

  21. 21

    Qt QMLで正方形のボタンを作成するにはどうすればよいですか?

  22. 22

    ポイントの周りに正方形を合わせるにはどうすればよいですか?

  23. 23

    カメを正方形の特定の領域に重ねるにはどうすればよいですか?

  24. 24

    この正方形を壁に跳ね返らせるにはどうすればよいですか?

  25. 25

    CSS-2つの正方形の中央に長方形を配置するにはどうすればよいですか

  26. 26

    Opencv:4つの台形画像をつなぎ合わせて正方形の画像を作成するにはどうすればよいですか?

  27. 27

    「正方形」の2次元配列をマッピングして座標を切り替えるにはどうすればよいですか?(ルビー)

  28. 28

    ブートストラップの2つの列の中央に正方形のdivを中央に配置するにはどうすればよいですか?

  29. 29

    ページ内に正方形(幅と高さの約80%、色付き)を追加するにはどうすればよいですか?

ホットタグ

アーカイブ