web-dev-qa-db-ja.com

opencvの実際の場所で画像ピクセルを取得するにはどうすればよいですか?

画像内のピクセルのRGBを取得したい。ただし、場所は整数の場所ではなく、実際の値(x、y)です。バイリニア補間値が必要です。どうすればopencvを実行できますか?

どうもありがとう

19
Shan

サブピクセルアクセスのための簡単な機能はありませんが、いくつかのオプションを提案できます。

  1. getRectSubPix を使用して、1ピクセル領域を抽出します。

    cv::Vec3b getColorSubpix(const cv::Mat& img, cv::Point2f pt)
    {
        cv::Mat patch;
        cv::getRectSubPix(img, cv::Size(1,1), pt, patch);
        return patch.at<cv::Vec3b>(0,0);
    }
    
  2. より柔軟で精度の低いものを使用する remap 1ピクセルマップで:

    cv::Vec3b getColorSubpix(const cv::Mat& img, cv::Point2f pt)
    {
        cv::Mat patch;
        cv::remap(img, patch, cv::Mat(1, 1, CV_32FC2, &pt), cv::noArray(),
            cv::INTER_LINEAR, cv::BORDER_REFLECT_101);
        return patch.at<cv::Vec3b>(0,0);
    }
    
  3. ロケット科学ではないため、双一次内挿を自分で実装します。

    cv::Vec3b getColorSubpix(const cv::Mat& img, cv::Point2f pt)
    {
        assert(!img.empty());
        assert(img.channels() == 3);
    
        int x = (int)pt.x;
        int y = (int)pt.y;
    
        int x0 = cv::borderInterpolate(x,   img.cols, cv::BORDER_REFLECT_101);
        int x1 = cv::borderInterpolate(x+1, img.cols, cv::BORDER_REFLECT_101);
        int y0 = cv::borderInterpolate(y,   img.rows, cv::BORDER_REFLECT_101);
        int y1 = cv::borderInterpolate(y+1, img.rows, cv::BORDER_REFLECT_101);
    
        float a = pt.x - (float)x;
        float c = pt.y - (float)y;
    
        uchar b = (uchar)cvRound((img.at<cv::Vec3b>(y0, x0)[0] * (1.f - a) + img.at<cv::Vec3b>(y0, x1)[0] * a) * (1.f - c)
                               + (img.at<cv::Vec3b>(y1, x0)[0] * (1.f - a) + img.at<cv::Vec3b>(y1, x1)[0] * a) * c);
        uchar g = (uchar)cvRound((img.at<cv::Vec3b>(y0, x0)[1] * (1.f - a) + img.at<cv::Vec3b>(y0, x1)[1] * a) * (1.f - c)
                               + (img.at<cv::Vec3b>(y1, x0)[1] * (1.f - a) + img.at<cv::Vec3b>(y1, x1)[1] * a) * c);
        uchar r = (uchar)cvRound((img.at<cv::Vec3b>(y0, x0)[2] * (1.f - a) + img.at<cv::Vec3b>(y0, x1)[2] * a) * (1.f - c)
                               + (img.at<cv::Vec3b>(y1, x0)[2] * (1.f - a) + img.at<cv::Vec3b>(y1, x1)[2] * a) * c);
    
        return cv::Vec3b(b, g, r);
    }
    
40
Andrey Kamaev

バイリニア補間とは、調べているピクセルに最も近い4つのピクセルに基づいて値に重みを付けることを意味します。重みは次のように計算できます。

cv::Point2f current_pos; //assuming current_pos is where you are in the image

//bilinear interpolation
float dx = current_pos.x-(int)current_pos.x;
float dy = current_pos.y-(int)current_pos.y;

float weight_tl = (1.0 - dx) * (1.0 - dy);
float weight_tr = (dx)       * (1.0 - dy);
float weight_bl = (1.0 - dx) * (dy);
float weight_br = (dx)       * (dy);

最終的な値は、各ピクセルとそれぞれの重みの積の合計として計算されます

4
Hammer

これを繰り返しまたは一貫して実行する場合は、マッピングを使用するとより効率的になります。もう1つの利点は、補間方法と境界条件の処理方法を選択することです。最後に、いくつかの補間関数もGPUに実装されています。 リマップ

2
Vlad

残念ながら、受け入れられた回答へのコメントとしてこれを投稿するのに十分なポイントがありません...フロートの単一チャネル行列での補間を必要とする自分の問題に合うようにコードを調整しました。

どちらのアプローチが最速かを直感的に理解してほしいと思いました。

Andrey Kamaevの回答からの3つのメソッドと、単純な最近傍(基本的には座標を四捨五入)を実装しました。

ガベージを埋めたばかりの行列A(100x100))で実験を実行し、値を埋めた行列B(400x400)を作成しました。 B(i、j)= A(i/4、j/4)のように補間されます。

各実行は1000回実行され、平均時間は次のとおりです。

  • 最近傍:2.173ミリ秒
  • getRectSubPix:26.506ミリ秒
  • 再マップ:114.265ミリ秒
  • マニュアル:5.086ミリ秒
  • borderInterpolateなしのマニュアル:3.842ミリ秒

したがって、実際の補間をあまり気にせず、値が必要な場合、特にデータが非常にスムーズに変化する場合は、超高速の最近傍です。それ以外の場合は、他の方法よりも一貫して高速であるように見えるので、手動の双一次補間を使用します。 (OpenCV 2.4.9-Ubuntu 15.10リポジトリ-2016年2月)。

4つの寄与ピクセルすべてがマトリックスの範囲内にあることがわかっている場合は、基本的に最近傍と時間的に同等にすることができますが、とにかく違いはごくわずかです。

2
kleinric