使用 OpenCV 在 Java 中进行反投影

Posted

技术标签:

【中文标题】使用 OpenCV 在 Java 中进行反投影【英文标题】:Back Projection in Java with OpenCV 【发布时间】:2013-06-07 07:19:18 【问题描述】:

我想使用反投影检测带有OpenCV 的图像中的特征。

首先,我很乐意计算单个彩色小图像的直方图,然后将其应用于更大的图像。然后我可以在此基础上构建更多内容。 有一个example in C++,我想在 Java 中做这样的事情。 遗憾的是,OpenCV 的 Java 接口没有很好的文档记录。

下面是我到目前为止的代码,但它不起作用(显然,否则我不会寻求帮助)。 如果有人可以帮助我让它工作找到一些好的文档用于 Java API,那就太好了!

import java.util.ArrayList;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;

public class ColorHistogramDetector extends ColorThresholdDetector 
    //private cvHistogram histogram;
    //histogram resolution for hue and saturation
    static final int hbins = 30;//, sbins = 32;

    public synchronized Mat detect(Mat inputFrame) 
        Mat calcFrame = new Mat();
        Imgproc.cvtColor(inputFrame, calcFrame, Imgproc.COLOR_RGB2HSV);

        Mat hue = calcFrame;
        ArrayList<Mat> dst = new ArrayList<Mat>();
        dst.add(hue);

        //create single color image
        Mat fillImg = new Mat(16, 16, CvType.CV_8UC3);
        fillImg.setTo(hsvColor);

        MatOfInt histSize=new MatOfInt(hbins,hbins);

        // hue varies from 0 to 179, see cvtColor
        // saturation varies from 0 (black-gray-white) to
        // 255 (pure spectrum color)
        MatOfFloat ranges = new MatOfFloat( 0,180,0,256 );

        Mat hist = new Mat();

        // we compute the histogram from the 0-th and 1-st channels
        MatOfInt channels = new MatOfInt(0, 1);

        ArrayList<Mat> fillImgs=new ArrayList<Mat>();
        fillImgs.add(fillImg);
        Imgproc.calcHist(fillImgs, channels, new Mat(), hist, histSize, ranges);

        outputFrame = new Mat();

        Imgproc.calcBackProject(dst, channels, hist, calcFrame, ranges, 1);

        int w = inputFrame.cols(); int h = inputFrame.rows();
        int bin_w = (int) Math.round( (double) w / hbins );
        Mat histImg = new Mat( w, h, CvType.CV_8UC3 );

        for( int i = 0; i < hbins; i ++ )  
           Core.rectangle( histImg, new Point( i*bin_w, h ), 
                           new Point( (i+1)*bin_w, 
                           h - Math.round( hist.get(0, i)[0]*h/255.0 ) ), 
                           new Scalar( 0, 0, 255 ), -1 ); 
        

        hist.release();
        fillImg.release();

        Imgproc.cvtColor(histImg, calcFrame, Imgproc.COLOR_RGB2HSV);

        return calcFrame;
    

【问题讨论】:

哪里不行?您是否遇到错误,或者结果与您的预期不同? calcHistcalcBackProject 之间似乎没有normalize(...)hist 的内容如何?它是 8UC1 类型或某种浮点格式 Java API 是从 C++ 头文件自动生成的。你可以在这里找到文档:docs.opencv.org/java 是的,但这对于在 C++ 中使用指针的函数没有帮助!如何在 Java 中使用它们? 如果您不时为您的代码设置样式,这将非常有帮助...不仅对正在阅读它的开发人员,而且对您更轻松地调试您的代码。 (在缩进上做更多工作!) 【参考方案1】:

您的代码中有几处看起来很奇怪,因此我对您的建议是首先密切关注本教程,然后将其更改为您的用例。

您似乎在某种程度上接近您正在遵循的教程,但看起来您正在将calcHist 应用于单个彩色图像。我看不出这有什么用,它通常应该是带有一些对象的 HSV 图像。另外,您缺少normalize 步骤。

为了帮助您,我已将 C++ Back Projection Tutorial 转换为 OpenCV4android 2.4.8。

虽然您使用的是 Java 而不是 Android,但 API 完全相同,只是样板输入/输出处理会有所不同。

Original C++ code can be found here.

对原始教程进行了一些非常小的改动,使其更适用于 Android,例如:

它处理实时摄像机图像,而不是静态图像; 使用触摸事件代替鼠标点击; 背投的输出显示在左上角,覆盖相机馈送; 添加了高斯模糊作为降噪功能。

我还没有彻底测试所有步骤,但最终结果看起来还不错。

注意:就目前而言,您需要触摸屏幕一次才能初始化背投...

大部分就到这里了,缺少的可以找here at GitHub:

private int outputWidth=300;
private int outputHeight=200;
private Mat mOutputROI;

private boolean bpUpdated = false;

private Mat mRgba;
private Mat mHSV;
private Mat mask;

private int lo = 20; 
private int up = 20;

public void onCameraViewStarted(int width, int height) 

    mRgba = new Mat(height, width, CvType.CV_8UC3);
    mHSV = new Mat();
    mIntermediateMat = new Mat();
    mGray = new Mat(height, width, CvType.CV_8UC1);
    mOutputROI = new Mat(outputHeight, outputWidth, CvType.CV_8UC1);

    mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);        


public Mat onCameraFrame(CvCameraViewFrame inputFrame) 
    Mat mCamera = inputFrame.rgba();

    Imgproc.cvtColor(mCamera, mRgba, Imgproc.COLOR_RGBA2RGB);

    Mat mOutputROI = mCamera.submat(0, outputHeight, 0, outputWidth);

    //Addition to remove some noise:
    Imgproc.GaussianBlur(mRgba, mRgba, new Size(5, 5), 0, Imgproc.BORDER_DEFAULT);

    Imgproc.cvtColor(mRgba, mHSV, Imgproc.COLOR_RGB2HSV_FULL);

    if(mask!=null)
        if(bpUpdated==false)
            mGray = histAndBackproj();
         else 
            bpUpdated = false;
        

        Imgproc.resize(mGray, mIntermediateMat, mOutputROI.size(), 0, 0, Imgproc.INTER_LINEAR);
        Imgproc.cvtColor(mIntermediateMat, mOutputROI, Imgproc.COLOR_GRAY2BGRA);
    

    return mCamera;


public boolean onTouch(View arg0, MotionEvent arg1)        
    Point seed = getImageCoordinates(mRgba, arg1.getX(), arg1.getY());

    int newMaskVal = 255;
    Scalar newVal = new Scalar( 120, 120, 120 );

    int connectivity = 8;
    int flags = connectivity + (newMaskVal << 8 ) + Imgproc.FLOODFILL_FIXED_RANGE + Imgproc.FLOODFILL_MASK_ONLY;

    Mat mask2 = Mat.zeros( mRgba.rows() + 2, mRgba.cols() + 2, CvType.CV_8UC1 );

    Rect rect = null;
    Imgproc.floodFill( mRgba, mask2, seed, newVal, rect, new Scalar( lo, lo, lo ), new Scalar( up, up, up), flags );

    // C++: 
    // mask = mask2( new Range( 1, mask2.rows() - 1 ), new Range( 1, mask2.cols() - 1 ) );
    mask = mask2.submat(new Range( 1, mask2.rows() - 1 ), new Range( 1, mask2.cols() - 1 ));

    mGray = histAndBackproj();
    bpUpdated = true;           

    return true;


  private Mat histAndBackproj() 
    Mat hist = new Mat();
    int h_bins = 30; 
    int s_bins = 32;

    // C++:
    //int histSize[] =  h_bins, s_bins ;
    MatOfInt mHistSize = new MatOfInt (h_bins, s_bins);

    // C++:
    //float h_range[] =  0, 179 ;
    //float s_range[] =  0, 255 ;     
    //const float* ranges[] =  h_range, s_range ;     
    //int channels[] =  0, 1 ;

    MatOfFloat mRanges = new MatOfFloat(0, 179, 0, 255);
    MatOfInt mChannels = new MatOfInt(0, 1);

    // C++:
    // calcHist( &hsv, 1, channels, mask, hist, 2, histSize, ranges, true, false );

    boolean accumulate = false;
    Imgproc.calcHist(Arrays.asList(mHSV), mChannels, mask, hist, mHistSize, mRanges, accumulate);

    // C++:
    // normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );        
    Core.normalize(hist, hist, 0, 255, Core.NORM_MINMAX, -1, new Mat());

    // C++:
    // calcBackProject( &hsv, 1, channels, hist, backproj, ranges, 1, true );        
    Mat backproj = new Mat();
    Imgproc.calcBackProject(Arrays.asList(mHSV), mChannels, hist, backproj, mRanges, 1);

    return backproj;



/**
 * Method to scale screen coordinates to image coordinates, 
 * as they have different resolutions.
 * 
 * x - width; y - height; 
 * Nexus 4: xMax = 1196; yMax = 768
 * 
 * @param displayX
 * @param displayY
 * @return
 */
private Point getImageCoordinates(Mat image, float displayX, float displayY)
    Display display = getWindowManager().getDefaultDisplay();       
    android.graphics.Point outSize = new android.graphics.Point();
    display.getSize(outSize);

    float xScale = outSize.x / (float) image.width();
    float yScale = outSize.y / (float) image.height();                  

    return new Point(displayX/xScale, displayY/yScale);

【讨论】:

我觉得我 +1 还不够。 很棒的评论!我很久没有用 OpenCV 做任何事情了,但正因为如此,我会再看一遍 :) 我原来的问题也在 Android 上,顺便说一句。【参考方案2】:

使用javaCV(openCV 的java 接口)。它使用与 openCV 相同的方法约定,并且有很多示例可以使用它。

【讨论】:

以上是关于使用 OpenCV 在 Java 中进行反投影的主要内容,如果未能解决你的问题,请参考以下文章

youcans 的 OpenCV 例程 200 篇111. 雷登变换反投影重建图像

youcans 的 OpenCV 例程 200 篇112. 滤波反投影重建图像

OpenCV竟然可以这样学!成神之路终将不远(二十五)

基于OpenCV进行图像拼接原理解析和编码实现(提纲 代码和具体内容在课件中)

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

OpenCV实战——基于反向投影直方图检测图像内容