c++opencv

Using img.at to set the color of individual pixels in OpenCV C++


I am relatively new to OpenCV. I want to set the colour of individual pixels using img.at I also kind of understand cv::Scalar, for example cv::Scalar(0,255,0) is green, cv::Scalar(255,0,0) is blue

I tried the following code

int main( void ){
  cv::Mat img(400,400, CV_8UC3);

  for(int i=0; i < 400; ++i){
    for(int j=0; j < 400; ++j){
      img.at<cv::Scalar>(i,j) = cv::Scalar(0,255,0);
    }
  }

  cv::imshow("Tetris", img);
  cv::moveWindow("Tetris",400,400);

  cv::waitKey(0);

}

It compiles without error messages, but if I launch the program, nothing is visible at all. How do you set the colour of an indiviual pixel to red, green or blue?


Solution

  • Your cv::Mat type is CV_8UC3, meaning it contains 3 channels, each of them a uchar.

    But cv::Scalar contains 4 elements, and by default each of them is a double.

    cv::Vec3b is a convenient type to represent an RGB value, where each channle is a uchar.

    Therefore instead of:

    img.at<cv::Scalar>(i,j) = cv::Scalar(0,255,0);
    

    Use:

    //-----vvvvvvvvv----------vvvvvvvvv------------
    img.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 255, 0);
    

    Note that although it doesn't matter in the posted example, the indices that you pass to cv::Mat:at should be row index (AKA y) first and column index (AKA x) second.

    A side note:
    Using cv::Mat::at in such a tight loop to set the pixels of the entire image is not very efficient (due to some validations performed in each at call).
    A better approach would be to iterate over the rows, and for each row iterate via a pointer to the beginning of the row with cv::Mat::ptr.
    Also you should be aware that if you need to set all the pixels of an image to the same value, you can use cv::Mat::setTo method.


    Following the request from the OP, here's an example of using cv::Mat::ptr to set image pixels more efficiently.
    As mentioned above, if in the real case all pixels should have the same value, you can simply use cv::Mat::setTo instead.

    for (int row = 0; row < img.rows; ++row) {
        auto* pRowData = img.ptr<cv::Vec3b>(row);
        for (int col = 0; col < img.cols; ++col) {
            pRowData[col] = cv::Vec3b(0, 255, 0); // assign the pixel with the relevant value (here green is just an example)
        }
    }