如何在Javacv中提取轮廓的宽度和高度?

5

我正在使用javacv包(Opencv)开发组件识别项目。我使用了一种方法来返回图像上的一组矩形作为“CvSeq”。我需要知道以下几点:

  • 如何从方法输出(从CvSeq)中获取每个矩形?
  • 如何访问矩形的长度和宽度?

这是返回矩形的方法:

public static CvSeq findSquares( final IplImage src,  CvMemStorage storage)
{

CvSeq squares = new CvContour();
squares = cvCreateSeq(0, sizeof(CvContour.class), sizeof(CvSeq.class), storage);

IplImage pyr = null, timg = null, gray = null, tgray;
timg = cvCloneImage(src);

CvSize sz = cvSize(src.width() & -2, src.height() & -2);
tgray = cvCreateImage(sz, src.depth(), 1);
gray = cvCreateImage(sz, src.depth(), 1);
pyr = cvCreateImage(cvSize(sz.width()/2, sz.height()/2), src.depth(), src.nChannels());

// down-scale and upscale the image to filter out the noise
cvPyrDown(timg, pyr, CV_GAUSSIAN_5x5);
cvPyrUp(pyr, timg, CV_GAUSSIAN_5x5);
cvSaveImage("ha.jpg",   timg);
CvSeq contours = new CvContour();
// request closing of the application when the image window is closed
// show image on window
// find squares in every color plane of the image
for( int c = 0; c < 3; c++ )
{
    IplImage channels[] = {cvCreateImage(sz, 8, 1), cvCreateImage(sz, 8, 1), cvCreateImage(sz, 8, 1)};
    channels[c] = cvCreateImage(sz, 8, 1);
    if(src.nChannels() > 1){
        cvSplit(timg, channels[0], channels[1], channels[2], null);
    }else{
        tgray = cvCloneImage(timg);
    }
    tgray = channels[c]; // try several threshold levels
    for( int l = 0; l < N; l++ )
    {
    //             hack: use Canny instead of zero threshold level.
    //             Canny helps to catch squares with gradient shading
                   if( l == 0 )
                {
    //                apply Canny. Take the upper threshold from slider
    //                and set the lower to 0 (which forces edges merging)
                      cvCanny(tgray, gray, 0, thresh, 5);
   //                 dilate canny output to remove potential
   //                // holes between edge segments
                      cvDilate(gray, gray, null, 1);
                 }
                 else
                 {
    //                apply threshold if l!=0:
                      cvThreshold(tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY);
                 }
    //            find contours and store them all as a list
                cvFindContours(gray, storage, contours, sizeof(CvContour.class), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

                CvSeq approx;

  //            test each contour
                while (contours != null && !contours.isNull()) {
                       if (contours.elem_size() > 0) {
                            approx = cvApproxPoly(contours, Loader.sizeof(CvContour.class),storage, CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0);
                    if( approx.total() == 4
                            &&
                            Math.abs(cvContourArea(approx, CV_WHOLE_SEQ, 0)) > 1000 &&
                        cvCheckContourConvexity(approx) != 0
                        ){
                        double maxCosine = 0;
                        //
                        for( int j = 2; j < 5; j++ )
                        {
           //         find the maximum cosine of the angle between joint edges
                      double cosine = Math.abs(angle(new CvPoint(cvGetSeqElem(approx, j%4)), new CvPoint(cvGetSeqElem(approx, j-2)), new CvPoint(cvGetSeqElem(approx, j-1))));
                       maxCosine = Math.max(maxCosine, cosine);
                         }
                         if( maxCosine < 0.2 ){
                             cvSeqPush(squares, approx);
                         }
                    }
                }
                contours = contours.h_next();
            }
        contours = new CvContour();
    }
}
return squares;
}

这是我使用的示例原始图像:

enter image description here

在绘制匹配矩形周围的线条后,这是我得到的图像:

enter image description here

实际上,在上述图像中,我正在尝试删除那些大矩形,并且只需要识别其他矩形,因此我需要一些代码示例来了解如何实现上述目标。请分享您的经验,谢谢!
2个回答

4

OpenCV可以在黑色背景中找到白色物体的轮廓。在您的情况下,情况相反,物体是黑色的。这样,甚至图像边框也是一个对象。因此,为了避免这种情况,只需反转图像,使背景变为黑色。

下面我用OpenCV-Python演示了它:

import numpy as np
import cv2

im = cv2.imread('sofsqr.png')
img = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

ret,thresh = cv2.threshold(img,127,255,1)

请记住,与其使用单独的函数进行反转,不如将其用于阈值处理中。只需将阈值类型转换为BINARY_INV(即'1')即可。
现在您有一幅如下所示的图像:
现在我们要找轮廓。然后对于每个轮廓,我们近似它并检查它是否是一个矩形,方法是查看近似轮廓的长度,它应该是四个矩形。
如果画出来,你会得到像这样的图像:
同时,我们还要找到每个轮廓的边界框。边界框的形状如下:[初始点x,初始点y,矩形的宽度,矩形的高度]
所以你就得到了宽度和高度。
以下是代码:
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    approx = cv2.approxPolyDP(cnt,cv2.arcLength(cnt,True)*0.02,True)
    if len(approx)==4:
        cv2.drawContours(im,[approx],0,(0,0,255),2)
        x,y,w,h = cv2.boundingRect(cnt)

编辑:

在一些评论的帮助下,我理解到这个问题的真正目的是避免选择大矩形,只选择较小的矩形。

可以使用我们获取的边界矩形值来实现。即,只选择那些长度小于阈值、宽度或面积的矩形。例如,在这张图片中,我选择的面积应该小于10000(一个粗略的估计)。如果面积小于10000,则将其选中并以红色表示,否则将其表示为虚假候选项,并以蓝色表示(仅供可视化参考)。

for cnt in contours:
    approx = cv2.approxPolyDP(cnt,cv2.arcLength(cnt,True)*0.02,True)
    if len(approx)==4:
        x,y,w,h = cv2.boundingRect(approx)
        if w*h < 10000:
            cv2.drawContours(im,[approx],0,(0,0,255),-1)
        else:
            cv2.drawContours(im,[approx],0,(255,0,0),-1)

以下是我得到的输出:
下面是我得到的输出:

enter image description here

如何获得阈值?这完全取决于您和您的应用程序。或者您可以通过试错方法找到它。(我就是这么做的)。
希望这能解决您的问题。所有函数都是标准的opencv函数,所以我认为您不会遇到任何问题来转换为JavaCV。

实际上,我需要从图像中删除两个大矩形,并识别其他小矩形。请问您能否解释如何使用javacv实现这一点。 - SL_User
2
K,我明白了...你只需要小矩形,而且要避免大矩形,对吧?如果它们的长度或宽度大于某个阈值,就可以避免它们。 - Abid Rahman K
3
找到边界矩形,它直接给出宽度和高度。请查看我博客中有关轮廓的文章。Opencvpython.blogspot.com - Abid Rahman K
谢谢您的回复,我已经能够访问这些矩形的长度和宽度。但是当我访问轮廓时,在上面的图像中显示了超过4个矩形。您能否解释一下原因? - SL_User
我正在使用Python。当我找到轮廓时,我会得到所有可能的轮廓。实际上,它是所有单独多边形的列表。现在我从这个列表中取出每个轮廓(for cnt in contours行)。然后我检查它是否为矩形。所以在所有轮廓中,有4个作为矩形。因此,当您访问它们时,会给出4个轮廓。您必须访问它们中的每一个。我不知道Java中是什么样子。早些时候,在Python中,它是一个cvSeq对象,并且有cvSeq.hnext()来逐个访问每个轮廓。现在它是一个列表。因此,您必须找出Java中的情况。 - Abid Rahman K
显示剩余2条评论

2

我注意到问题提供的代码中存在一个bug:

IplImage channels[] = {cvCreateImage(sz, 8, 1), cvCreateImage(sz, 8, 1), cvCreateImage(sz, 8, 1)};
channels[c] = cvCreateImage(sz, 8, 1);
if(src.nChannels() > 1){
    cvSplit(timg, channels[0], channels[1], channels[2], null);
}else{
    tgray = cvCloneImage(timg);
}
tgray = channels[c];

这意味着如果只有一个通道,tgray将是一张空白图片。 应该这样写:
IplImage channels[] = {cvCreateImage(sz, 8, 1), cvCreateImage(sz, 8, 1), cvCreateImage(sz, 8, 1)};
channels[c] = cvCreateImage(sz, 8, 1);
if(src.nChannels() > 1){
    cvSplit(timg, channels[0], channels[1], channels[2], null);
    tgray = channels[c];
}else{
    tgray = cvCloneImage(timg);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接