使用 OpenCV 的 Java 中的 Back Projection

2022-09-01 17:49:16

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

首先,我很乐意计算单个彩色小图像的直方图,然后将其应用于较大的图像。然后,我可以在此基础上构建更多内容。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;
    }
}

答案 1

在你的代码中,有几件事看起来很奇怪,所以我对你的建议是首先密切关注本教程,然后将其更改为你的用例。

您似乎以某种方式接近您正在遵循的教程,但看起来您正在应用于单个彩色图像。我不明白这有什么用处,它通常应该是带有某些对象的HSV图像。此外,您缺少该步骤。calcHistnormalize

为了帮助您,我已将C++背面投影教程转换为OpenCV4Android 2.4.8。

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

原始C++代码可以在这里找到。

对原始教程进行了一些非常小的更改,以使其在Android上更可用,例如:

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

Back Projection Screenshot

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

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

以下是大部分内容,您可以在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);
}

答案 2

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


推荐