ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • OpenCV C++ 강좌 - 4. Mat에 저장된 이미지 픽셀 값에 접근하는 방법 비교
    OpenCV/OpenCV C++ 강좌 2019. 8. 9. 22:24



    OpenCV에서 Mat에 저장되어 있는 이미지의 픽셀에 직접 접근하는 방법을 소개합니다.  at, ptr, data 방법 별로 컬러 이미지의 모든 픽셀을 스캔하여 그레이스케일로 2000번 변환하는데 걸리는 시간을 측정해봅니다.   


    테스트 이미지로 다음 링크에 있는 고해상도 이미지를 사용했습니다.

    http://www.wallpaperawesome.com/wallpapers-awesome/wallpapers-full-hd-1920-1080-widescreen-awesome/z-wallpaper-incredible-sea-at-sunset-1920-x-1080-full-hd.jpg



    C++ 언어를 사용한 OpenCV 강좌를 진행하고 있습니다.

     


    OpenCV C++ 강좌 - 1. 이미지 파일을 읽어서 화면에 보여주기

    https://webnautes.tistory.com/1163


    OpenCV C++ 강좌 - 2. 컬러 이미지 파일을 읽어서 그레이스케일 이미지로 변환하여 저장하기

    https://webnautes.tistory.com/1166


    OpenCV C++ 강좌 - 3. 영상처리 기초 개념, pixel

    https://webnautes.tistory.com/1168


     

     



    1. Mat::at


    2. Mat::ptr


    3. Mat::data


    4. 성능 비교 테스트 결과





    다음 유트브 영상에서는  OpenCV에서 이미지 픽셀에 접근하는 방법을 픽셀 개념부터 설명하고 있습니다.

    한번 보면 이해하는데 많은 도움이 될 듯합니다.



    1. Mat::at

    Mat::at 메소드는 접근할 픽셀의 (y, x)좌표만 알고 있으면 이미지의 픽셀에 접근할 수 있는  방법입니다.

    하지만 매번 픽셀 위치를 계산하기 때문에 속도가 느립니다.


    3채널 픽셀 접근시에는 at 다음에 Vec3b 타입을 지정해주고 좌표 다음에 몇번째 채널을 가져올지 대괄호 [] 안에  인덱스를 적어줍니다.

    1채널 픽셀 접근시에는 at 다음에 uchar 타입을 지정해줍니다.


    for (int y = 0; y < height; y++) {

    for (int x = 0; x < width; x++) {


      uchar b = img_color.at<Vec3b>(y, x)[0];
    uchar g = img_color.at<Vec3b>(y, x)[1];
    uchar r = img_color.at<Vec3b>(y, x)[2];

    img_grayscale.at<uchar>(y, x) = (r + g + b) / 3.0;
    }
    }




    2. Mat::ptr

    포인터를 사용하는 방법으로  at 방법에 비해 빠른 속도를 보여줍니다.  현재 row의 포인터를 지정한 타입으로 미리 계산해서 가져오기 때문입니다.

    OpenCV의 Mat 구조상 row의 포인터를 한번 가져오고 이후 사용하면 픽셀 위치 계산이 빠르게 됩니다.


    for (int y = 0; y < height; y++) {


         // y번째 row에 대한 주소를 포인터에 저장한 후
    uchar* pointer_input = img_color.ptr<uchar>(y);
    uchar* pointer_ouput = img_grayscale.ptr<uchar>(y);


    for (int x = 0; x < width; x++) {


               // row 포인터로부터 (x * 3 )번째 떨어져 있는 픽셀을 가져옵니다.

               //0, 1, 2 순서대로 blue, green, red 채널값을 가져올 수있는 이유는 하나의 픽셀이 메모리상에 b g r 순서대로 저장되기 때문입니다.

    uchar b = pointer_input[x * 3 + 0];
    uchar g = pointer_input[x * 3 + 1];
    uchar r = pointer_input[x * 3 + 2];

    pointer_ouput[x] = (r + g + b) / 3.0;
    }
    }




    3. Mat::data

    Mat 클래스의 data에 직접 접근하는 방법으로 ptr과 비슷한 성능을 보여줍니다.


    for (int y = 0; y < height; y++) {

    uchar *data_output = img_grayscale.data;


    for (int x = 0; x < width; x++) {

    uchar b = data_input[y * width * 3 + x * 3];
    uchar g = data_input[y * width * 3 + x * 3 + 1];
    uchar r = data_input[y * width * 3 + x * 3 + 2];


    data_output[width * y + x] = (r + g + b) / 3.0;
    }
    }



    4. 성능 비교 테스트 결과


    각각의 방법에 대해서 2000번 반복하여 컬러이미지를 그레이스케일 이미지로 변환하는데 걸리는 시간을 측정하였습니다.

    Visual Studio에서 Release 모드로  Ctrl + F5를 눌러서 디버거 없이 실행한 결과입니다.


    at 방법이 가장 느리고  ptr과 data가 비슷한 성능을 보입니다.


    1차 시도

    time at =  10.0573 sec

    time ptr =  5.81874 sec

    time data =  5.60655 sec



    2차 시도

    time at =  10.0383 sec

    time ptr =  5.7141 sec

    time data =  5.61314 sec



    3차 시도

    time at =  10.0553 sec

    time ptr =  5.62628 sec

    time data =  5.59577 sec




    테스트 환경은 Intel i5-7200U ( Intel HD Graphics 620 ), Windows 10 Pro 64bit 입니다. 테스트한 환경마다 또는 테스트한 방법에 따라 결과에 차이가 있을 수 있습니다.


    특히 노트북의 경우에는 전원 모드를 최고 성능으로 하지 않으면 노트북의 성능이 제대로 나오지 않을 수 있습니다.




    테스트에 사용한 코드입니다.


    Mat::at

    #include <opencv2/opencv.hpp>
    #include <iostream>

    using namespace cv;
    using namespace std;



    int main()
    {
    Mat img_color = imread("fullhd.jpg", IMREAD_COLOR);


    int height = img_color.rows;
    int width = img_color.cols;


    Mat img_grayscale(height, width, CV_8UC1);


    double t1 = (double)getTickCount();


    for (int i = 0; i < 2000; i++)
    for (int y = 0; y < height; y++) {


    for (int x = 0; x < width; x++) {

    uchar b = img_color.at<Vec3b>(y, x)[0];
    uchar g = img_color.at<Vec3b>(y, x)[1];
    uchar r = img_color.at<Vec3b>(y, x)[2];

    img_grayscale.at<uchar>(y, x) = (r + g + b) / 3.0;
    }
    }

    t1 = ((double)getTickCount() - t1) / getTickFrequency();


    cout << "time at =  " << t1 << " sec" << endl;


    imshow("color", img_color);
    imshow("grayscale", img_grayscale);

    waitKey(0);
    return 0;
    }




    Mat::ptr


    #include <opencv2/opencv.hpp>
    #include <iostream>

    using namespace cv;
    using namespace std;



    int main()
    {
    Mat img_color = imread("fullhd.jpg", IMREAD_COLOR);


    int height = img_color.rows;
    int width = img_color.cols;


    Mat img_grayscale(height, width, CV_8UC1);


    double t2 = (double)getTickCount();

    for (int i = 0; i < 2000; i++)
    for (int y = 0; y < height; y++) {

    uchar* pointer_input = img_color.ptr<uchar>(y);
    uchar* pointer_ouput = img_grayscale.ptr<uchar>(y);
    for (int x = 0; x < width; x++) {

    uchar b = pointer_input[x * 3 + 0];
    uchar g = pointer_input[x * 3 + 1];
    uchar r = pointer_input[x * 3 + 2];

    pointer_ouput[x] = (r + g + b) / 3.0;
    }
    }

    t2 = ((double)getTickCount() - t2) / getTickFrequency();


    cout << "time ptr =  " << t2 << " sec" << endl;


    imshow("color", img_color);
    imshow("grayscale", img_grayscale);

    waitKey(0);
    return 0;
    }




    Mat::data


    #include <opencv2/opencv.hpp>
    #include <iostream>

    using namespace cv;
    using namespace std;



    int main()
    {
    Mat img_color = imread("fullhd.jpg", IMREAD_COLOR);


    int height = img_color.rows;
    int width = img_color.cols;


    Mat img_grayscale(height, width, CV_8UC1);


    double t3 = (double)getTickCount();


    uchar *data_input = img_color.data;

    for (int i = 0; i < 2000; i++)
    for (int y = 0; y < height; y++) {

    uchar *data_output = img_grayscale.data;


    for (int x = 0; x < width; x++) {

    uchar b = data_input[y * width * 3 + x * 3];
    uchar g = data_input[y * width * 3 + x * 3 + 1];
    uchar r = data_input[y * width * 3 + x * 3 + 2];


    data_output[width * y + x] = (r + g + b) / 3.0;
    }
    }

    t3 = ((double)getTickCount() - t3) / getTickFrequency();


    cout << "time data =  " << t3 << " sec" << endl;


    imshow("color", img_color);
    imshow("grayscale", img_grayscale);

    waitKey(0);
    return 0;
    }




    CV_32FC3 타입으로 변환 후 값이 이상해진다고 물어보셔서 테스트한 코드를 추가합니다.

    CV_32FC3로 변환후에도 같은 픽셀 값이 출력되는 것을 확인할 수 있습니다.  


    #include <opencv2/opencv.hpp>
    #include <iostream>

    using namespace cv;
    using namespace std;



    int main()
    {
    Mat img_color = imread("1.jpg", IMREAD_COLOR);


    int height = img_color.rows;
    int width = img_color.cols;


    int x = 50;
    int y = 50;


    uchar *data = img_color.data;
    int b = data[y * width * 3 + x * 3];
    int g = data[y * width * 3 + x * 3 + 1];
    int r = data[y * width * 3 + x * 3 + 2];

    cout << b << " " << g << " " << r <<endl;


    Mat newImage;
    img_color.convertTo(newImage, CV_32FC3, 1);


    float *data2 = (float*)newImage.data;
    b = data2[y * width * 3 + x * 3];
    g = data2[y * width * 3 + x * 3 + 1];
    r = data2[y * width * 3 + x * 3 + 2];

    cout << b << " " << g << " " << r <<endl;

    return 0;
    }





    마지막 업데이트 2019. 8. 9


     


    포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
    댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

    여러분의 응원으로 좋은 컨텐츠가 만들어집니다. 지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

    유튜브 구독하기


    TAG

    댓글 34

    • 1234 2019.05.15 12:54


      안녕하세요!
      320x240 이미지를 inrange 함수를 통해서 이진화를 시켰을 경우
      1의 값과 0의 값의 개수를 알고 싶은데 어떻게 하면 그 값을 추출할 수 있을까요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.05.15 14:02 신고


        다음 포스트를 참고하여 바이너리 이미지를 1과 0으로 구성된 텍스트로 변경한 후 세면 되지 않을까 싶습니다.

        https://webnautes.tistory.com/1293

    • 1234 2019.05.15 16:10


      감사합니다. 했습니다!

    • 1234 2019.05.15 16:19


      위와 같은 방법으로 하였을 경우
      수행시간이 6초정도 걸리는데
      이를 한번에 처리하는 함수는 따로 없는걸까요?!

    • 1126 2019.07.19 01:58


      안녕하세요 !
      이미지 2개 각각 A B라고했을때
      특정영역을 잘라서 PIXEL INTENSITY값의 차이를 구하려고하는데

      음수가 나오는 경우 -1이면 255 -2이면 254 이렇게 나오는데
      ABS함수 써도 1 2값을 얻지 못하는데 방법이 있을까요?

    • 깐박 2019.08.09 15:17


      영상에서 한 프레임을 따서 위와같이 pixel접근했는데

      data에 직접접근하는 코드가 저게 맞나요??

      아무리 값을 하나하나 다찍어봐도 오버랩 되거나 픽셀 위치를 잘못찾아간거같아서요

      • 관호 2019.08.09 20:12


        그러면 제가 RGB픽셀별로구분하지않고 픽셀하나하나를 동일하게 처리하려면 저코드가 맞지않나요?

        그리고 RGB별로 구분해서 처리해도 값이 달라지는 문제는 동일해서요

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 20:51 신고


        간단한 이미지로 테스트해서 어떤 것을 해야 맞는지 확인 해보세요..

    • 깐박 2019.08.09 16:13


      영상에서 한 프레임을 따서 위와같이 pixel접근했는데

      data에 직접접근하는 코드가 저게 맞나요??

      픽셀하나만 뽑아봤는데 frame의 첫번쨰 픽셀과 uchar로 뽑은 값이 다르더라구요...

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 16:44 신고


        다음처럼 해보면 data, at 방식에서 같은 위치의 픽셀값을 가져오는 것을 확인할 수 있습니다.

        https://github.com/webnautes/nudapeu/blob/master/pixel.cpp

      • 깐박 2019.08.09 17:11


        제가 영상에서 3채널 짜리 한 프레임을 받아와서

        그것을 CV_32FC3로 변경후 일정작업을 거친후

        픽셀단위로 값을 동일하게 받아오고싶은데
        uchar로 받아오든 float으로 받아오든 값은 동일한데 그값이

        일정작업을 마친 frame의 데이터와 값이 일치하지를 않습니다.
        frame데이터의 하나의 픽셀이 0.3정도이면
        uchar 나 float으로 받아온 데이터는
        1.5 *10^(-40)정도로 나오고

        체크해보려고 mat::ones를 생성후 그값을 uchar나 float으로 받아오려고했는데 1,0,01,0,0,1,0,0이런식으로 출력되고
        위의 모든 Mat크기를 1920 by 1080로 생성했는데 값을 1/5정도면 받고 나머지는 0으로 생성되어서요...
        제 생각은 자료형의 문제인것같은데 원인을 알수가없네요..

      • 깐박 2019.08.09 17:24


        이런식으로 코드를 짜서 최종적으로 B의 값을 frame의 값과 pixel마다 동일하게 받아오고싶은데 값이 다르네요...
        참고로 frame인자는 CV_32FC3입니다.
        for (int y = 0; y < h; y++) {
        for (int x = 0; x < 3 * w; x++) {
        b = frame.data[y * w * 3 + x];

        B.data[y * w * 3 + x] = b;

        }
        }

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 18:12 신고


        data 사용시 x좌표에서 3을 곱해줘야 하는데 올린 코드에는 빠져있네요.

        uchar b = data_input[y * width * 3 + x * 3];

        그리고 x좌표의 범위에 3을 곱해놓으셨네요...
        x<3*w

        이렇게 바꾸면 문제가 생길 수 있을 듯합니다.

      • 관호 2019.08.09 18:54


        RGBRGB순이면 제가 RGB로 나누지않고 처리하려고해서 저런코드로했는데
        문제없지않나요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 19:02 신고


        포스트에 있는 코드는 x방향으로 3열씩 옆으로 이동하며 B,G,R을 하나씩 가져와 처리합니다.

        결과적으로 한 픽셀의 3채널을 한번에 접근하게 됩니다.

      • 관호 2019.08.09 20:46


        RGB를 한번에 접근하는건 코드보고이해하겠는데

        굳이 한번에 접근안하고 for문 한번에 하나에 픽셀에 접근하는 식이면 저렇게 짜는게 맞지않나요?

        그리고 B,G,R로 한번에 접근을 해도 입력된 frame의 pixel 데이터와 uchar로 frame의 pixel하나를 받아오는 값과 달라서요

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 20:53 신고


        BGR 3개의 채널이 하나의 픽셀이라는 점을 감안하고 작성한다면 어떤 방법을 쓰던 상관없습니다.

        간단한 이미지를 가지고 머가 문제인지 테스트해볼 필요가 있을 듯합니다.

      • 관호 2019.08.09 20:57


        for문의 코드를 보면
        Mat파일은 3채널일 경우

        1*1 1*2 1*3 1*4 1*5 1*6 ... 1920*1080
        B , G , R , B , G , R ... R

        으로 구성되있는게 맞죠?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 21:27 신고


        다음 포스트에 있는 3번째 이미지처럼 픽셀이 화면에 배치되어 있습니다
        .
        OpenCV에서 이미지의 픽셀에 접근하는 방법
        https://webnautes.tistory.com/1359

      • 관호 2019.08.09 21:48


        그런데 왜 for문에서 data로 접근시에는 하나의 array처럼 접근하죠?

        예를 들면

        (1*1) (2*1) (3*1) ...(1898*1080) (1899*1080) (1920*1080) 이런식으로요

        보내주신 링크의 3번째 사진은 "이런 모양으로 데이터를 맵핑한다" 라는걸 보여주는 사진아닌가요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 21:52 신고


        이미지의 픽셀은 3번쨰 이미지처럼 구성되어 있습니다.
        각 픽셀은 하나의 채널(그레이스케일 이미지) 또는 3개의 채널(컬러이미지)로 구성되고요

        픽셀에 접근시 at은 2차원 (y,x)좌표로 접근하는 방식이고

        data는 사용하면 마치 1차원 배열에 픽셀이 저장된 것처럼 접근합니다.

      • 관호 2019.08.09 22:06


        짜고있는 코드가 속도를 요하는 작업이라

        data방식을 써야하는데

        간단하게
        frame.data[y*w*3 + x*3]의 픽셀이 2의 값을 가지고있고 CV_32FC3의 type이며

        frame.data[y*w*3 + x*3] = frame.data[y*w*3 + x*3] - 1;로 만든뒤

        uchar b = frame.data[y*w*3 + x*3]; 로 pixel 값을 받아와서
        b로 결과를 출력하려니까

        2-1 = 1이므로 1이나와야하는데 이상한값이 나오네요 아무리 찾아봐도 32F type의 값에 그냥 -1을 하려면 어떻게 해야하는지 자료가 없어서요...
        float의 1로 뺴고 int 1로 뺴고 uchar 1로 빼는 방법을 다해봐도 동일하게 이상한 0.9960~~같은 값이 나오네요..

      • 관호 2019.08.09 22:11


        참고로 pixel로 접근안하고 Mat + Mat방식으로 하면 입력값과 출력값이 동일한데

        Mat의 pixel값을 받아오면 이상한값으로 변하네요...

        Mat의 (1,1)의 픽셀값이 1이면
        ucahr로 (1,1)을 받으면 1이 저장되는게 아니고 이상한값이 저장되있네요...

        아무리 봐도 형식자의 문제인거같은데
        위의 올리신 data접근방식은 Mat의 pixel에서 uchar로 값을 받아온뒤 uchar 형식 output의 pixel에 uchar의 값을 저장후 image로 보여주니까 픽셀단위로 접근한건맞는데 최초의 img값과 uchar로 뽑은 값이 다를듯싶네요
        혹시 확인해보신건가요?
        저는 Mat값과 동일한 값을 받아서 처리후 Mat에 저장후 데이터로 저장하고싶어서요

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 22:25 신고


        본 포스트 마지막에 테스트한 코드를 추가해놓았습니다. 검토해보세요.

      • 관호 2019.08.09 22:30


        위에 주신 코드 돌려보니 값은 동일하게 찍히네요

        pixel로 접근했는데 계속해서 이상한 값이 계속해서 생성되서 질문을 이렇게 많이 했네요
        감사합니다. 다시 시도해보겠습니다.

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 22:47 신고


        조바심에 말하면..data사용시 float 포인터를 사용해야 값이 제대로 찍힙니다.

        성공하시길..

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.09 23:13 신고


        BGR을 분리한거 같은데 b,g,r이 1채널 이미지 아닌가요?
        다음처럼 해야 할 듯합니다

        float BB = b[y * w + x ];
        float GG = g[y * w + x + 1];
        float RR = r[y * w + x + 2];

    • 감사합니다 2019.09.17 12:35


      저는 필터를 CV_8UC3를 사용하는데 CV_8UC1이랑 무슨차이인가요??

    • 2019.11.25 11:58


      비밀댓글입니다

    • 山川康彦 2020.02.06 13:45


      ありがとうございました。

Designed by Tistory.