web-dev-qa-db-ja.com

OpenCVとPythonを使用して、接触/重複する円/楕円を検出する

円の真円度を測定したい(「円」の高さと幅または楕円パラメータの差)。円はここに示されているように写真で与えられます:

Color2gray、しきい値処理、境界検出などの通常の処理を行った後、次の画像が表示されます。

これで、私はすでに多くの異なることを試しました:

  • FindContourを使用してリスト項目Watershed( this question に類似)-> openCVは、閉じた輪郭を形成せずにくっついているため、円ではなく、閉じた輪郭として円間のスペースを検出します
  • fitEllipseと同じ問題。楕円が黒い背景の輪郭にフィットしていて、間にはありません。
  • ハフ変換を適用しようとすると(コードと3番目の図のように)、奇妙な結果が生じます。

こちらのコードをご覧ください。

import sys
import cv2
import numpy
from scipy.ndimage import label

# Application entry point
#img = cv2.imread("02_adj_grey.jpg")
img = cv2.imread("fuss02.jpg")

# Pre-processing.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    
cv2.imwrite("SO_0_gray.png", img_gray)

#_, img_bin = cv2.threshold(img_gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)
_, img_bin = cv2.threshold(img_gray, 170, 255, cv2.THRESH_BINARY)
cv2.imwrite("SO_1_threshold.png", img_bin)

#blur = cv2.GaussianBlur(img,(5,5),0)
img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_CLOSE, numpy.ones((3, 3), dtype=int))
cv2.imwrite("SO_2_img_bin_morphoEx.png", img_bin)

border = img_bin - cv2.erode(img_bin, None)
cv2.imwrite("SO_3_border.png", border)


circles = cv2.HoughCircles(border,cv2.cv.CV_HOUGH_GRADIENT,50,80, param1=80,param2=40,minRadius=10,maxRadius=150)
print circles

cimg = img
for i in circles[0,:]:
# draw the outer circle
    cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
    # draw the center of the circle
    cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
    cv2.putText(cimg,str(i[0])+str(',')+str(i[1]), (i[0],i[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255)

cv2.imwrite("SO_8_cimg.png", cimg)

誰かが私のアルゴリズムを改善するアイデアや完全に異なるアプローチを持っていますか?私はさまざまなアプローチを試みてきましたが、今のところ運がありません。皆さんの助けに感謝します。

21
Merlin

これが、サークルを検出する私の試みです。要約すれば

  • bGR-> HSV変換を実行し、Vチャネルを処理に使用する

Vチャンネル:

enter image description here

  • しきい値、モルフォロジークロージングを適用し、距離変換を行います(これをdistと呼びます)

dist画像:

enter image description here

  • テンプレートを作成します。画像内の円のサイズから、半径75ピクセル以下のディスクは妥当に見えます。その距離変換を取り、それをテンプレートとして使用します(これをtempと呼びます)

temp画像:

enter image description here

  • テンプレートマッチングを実行:dist * temp

dist * temp画像:

enter image description here

  • 結果の画像の極大値を見つけます。最大値の位置は円の中心に対応し、最大値はそれらの半径に対応します

しきい値に一致するテンプレートに一致する画像:

enter image description here

極大としての円の検出:

enter image description here

私はそれに最も慣れているので、これをC++で行いました。これがpythonに簡単に変換できると思います。上の画像は実物大ではないことに注意してください。これがお役に立てば幸いです。

編集:Pythonバージョンを追加

C++:

    double min, max;
    Point maxLoc;

    Mat im = imread("04Bxy.jpg");
    Mat hsv;
    Mat channels[3];
    // bgr -> hsv
    cvtColor(im, hsv, CV_BGR2HSV);
    split(hsv, channels);
    // use v channel for processing
    Mat& ch = channels[2];
    // apply Otsu thresholding
    Mat bw;
    threshold(ch, bw, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    // close small gaps
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
    Mat morph;
    morphologyEx(bw, morph, CV_MOP_CLOSE, kernel);
    // take distance transform
    Mat dist;
    distanceTransform(morph, dist, CV_DIST_L2, CV_DIST_MASK_PRECISE);
    // add a black border to distance transformed image. we are going to do
    // template matching. to get a good match for circles in the margin, we are adding a border
    int borderSize = 75;
    Mat distborder(dist.rows + 2*borderSize, dist.cols + 2*borderSize, dist.depth());
    copyMakeBorder(dist, distborder, 
            borderSize, borderSize, borderSize, borderSize, 
            BORDER_CONSTANT | BORDER_ISOLATED, Scalar(0, 0, 0));
    // create a template. from the sizes of the circles in the image, 
    // a ~75 radius disk looks reasonable, so the borderSize was selected as 75
    Mat distTempl;
    Mat kernel2 = getStructuringElement(MORPH_ELLIPSE, Size(2*borderSize+1, 2*borderSize+1));
    // erode the ~75 radius disk a bit
    erode(kernel2, kernel2, kernel, Point(-1, -1), 10);
    // take its distance transform. this is the template
    distanceTransform(kernel2, distTempl, CV_DIST_L2, CV_DIST_MASK_PRECISE);
    // match template
    Mat nxcor;
    matchTemplate(distborder, distTempl, nxcor, CV_TM_CCOEFF_NORMED);
    minMaxLoc(nxcor, &min, &max);
    // threshold the resulting image. we should be able to get peak regions.
    // we'll locate the peak of each of these peak regions
    Mat peaks, peaks8u;
    threshold(nxcor, peaks, max*.5, 255, CV_THRESH_BINARY);
    convertScaleAbs(peaks, peaks8u);
    // find connected components. we'll use each component as a mask for distance transformed image,
    // then extract the peak location and its strength. strength corresponds to the radius of the circle
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(peaks8u, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
    for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
    {
        // prepare the mask
        peaks8u.setTo(Scalar(0, 0, 0));
        drawContours(peaks8u, contours, idx, Scalar(255, 255, 255), -1);
        // find the max value and its location in distance transformed image using mask
        minMaxLoc(dist, NULL, &max, NULL, &maxLoc, peaks8u);
        // draw the circles
        circle(im, maxLoc, (int)max, Scalar(0, 0, 255), 2);
    }

Python:

import cv2

im = cv2.imread('04Bxy.jpg')
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
th, bw = cv2.threshold(hsv[:, :, 2], 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
morph = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
dist = cv2.distanceTransform(morph, cv2.cv.CV_DIST_L2, cv2.cv.CV_DIST_MASK_PRECISE)
borderSize = 75
distborder = cv2.copyMakeBorder(dist, borderSize, borderSize, borderSize, borderSize, 
                                cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
gap = 10                                
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*(borderSize-gap)+1, 2*(borderSize-gap)+1))
kernel2 = cv2.copyMakeBorder(kernel2, gap, gap, gap, gap, 
                                cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
distTempl = cv2.distanceTransform(kernel2, cv2.cv.CV_DIST_L2, cv2.cv.CV_DIST_MASK_PRECISE)
nxcor = cv2.matchTemplate(distborder, distTempl, cv2.TM_CCOEFF_NORMED)
mn, mx, _, _ = cv2.minMaxLoc(nxcor)
th, peaks = cv2.threshold(nxcor, mx*0.5, 255, cv2.THRESH_BINARY)
peaks8u = cv2.convertScaleAbs(peaks)
contours, hierarchy = cv2.findContours(peaks8u, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
peaks8u = cv2.convertScaleAbs(peaks)    # to use as mask
for i in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[i])
    _, mx, _, mxloc = cv2.minMaxLoc(dist[y:y+h, x:x+w], peaks8u[y:y+h, x:x+w])
    cv2.circle(im, (int(mxloc[0]+x), int(mxloc[1]+y)), int(mx), (255, 0, 0), 2)
    cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 255), 2)
    cv2.drawContours(im, contours, i, (0, 0, 255), 2)

cv2.imshow('circles', im)
47
dhanushka

コード@dhanuskhaでエラーが発生しました。別のバージョンのCVを使用しているためだと思います。このコードは、必要に応じてCV 3.0で動作します。

import cv2

im = cv2.imread('input.png')
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
th, bw = cv2.threshold(hsv[:, :, 2], 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
morph = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
dist = cv2.distanceTransform(morph, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
borderSize = 75
distborder = cv2.copyMakeBorder(dist, borderSize, borderSize, borderSize, borderSize, 
                                cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
gap = 10                                
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*(borderSize-gap)+1, 2*(borderSize-gap)+1))
kernel2 = cv2.copyMakeBorder(kernel2, gap, gap, gap, gap, 
                                cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
distTempl = cv2.distanceTransform(kernel2, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
nxcor = cv2.matchTemplate(distborder, distTempl, cv2.TM_CCOEFF_NORMED)
mn, mx, _, _ = cv2.minMaxLoc(nxcor)
th, peaks = cv2.threshold(nxcor, mx*0.5, 255, cv2.THRESH_BINARY)
peaks8u = cv2.convertScaleAbs(peaks)
_, contours, hierarchy = cv2.findContours(peaks8u, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
peaks8u = cv2.convertScaleAbs(peaks)    # to use as mask
for i in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[i])
    _, mx, _, mxloc = cv2.minMaxLoc(dist[y:y+h, x:x+w], peaks8u[y:y+h, x:x+w])
    cv2.circle(im, (int(mxloc[0]+x), int(mxloc[1]+y)), int(mx), (255, 0, 0), 2)
    cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 255), 2)
    cv2.drawContours(im, contours, i, (0, 0, 255), 2)

cv2.imshow('circles', im)
cv2.waitKey(0)