Thứ tự của mô hình màu RBG trong opencv là gì

Hello
Theo mình biết trong opencv sử dụng mô hình màu RBG theo thứ tự BGR. Vấn đề là mấy tháng trước mình cũng làm 1 project có dùng opencv để hiển thị ảnh xem kết quả. Thì thứ tự màu lại khác (lúc đó mình dùng bản 3.0.0 beta). Mình đẩy dữ liệu từ array (kiểu Matrix do mình tạo ra và đọc dữ liệu vào đó) vào Mat của opencv như sau

Giờ mình cũng đang làm 1 project khác có sử dụng opencv (bản 3.2) thì dùng thứ tự như trên lại bị lỗi màu ngay và mình thử làm như thế này

khác nhau ở chỗ

pixel[0]
pixel[2] 
pixel[1] 

với

pixel[2] 
pixel[1] 
pixel[0] 

thật sự không biết kênh nào là b, kênh nào là g, kênh nào là r nữa :smile:

(2 lần đẩy array qua Mat trên 1 cái sử dụng array 1d, 2 cái 2d, và cùng giải mã ảnh theo 1 kiểu, 1 ảnh 3x3, mỗi pixel có 3 kênh, thì array 2d sẽ có 3x9 phần tử, array 1d có 27 phần tử, nghĩa là thứ tự pixel giống nhau -> vấn đề là ở cái thứ tự mô hình RBG trong opencv)
vậy chốt lại

  pixel[0] 
  pixel[1] 
  pixel[2] 
cái nào là r, cái nào là g, cái nào là b ???

Thank you

Bạn phải từng bước kiểm tra xem nó sai ở bước nào. Từ input hay phần hiển thị.

Thứ nhật, Phải kiểm tra input của nó là gì ? Camera hay tập tin ảnh.

Với camera thì nó là BGR, có RGB thì chưa gặp bao giờ (có thể có nhưng chưa gặp).
Với file thì hên xui. Có thể người ta lưu RGB hoặc BGR.
Nếu lưu với RGB => sai ngay từ input.

Thứ hai, Kiểm tra thành phần hiển thị là thành phần nào ?
Cùng với dữ lieu BGR hay RGB thì khi sử dung các thành phần khác nhau để hiển thị ảnh thì nó sẽ nhận kênh 0 là R hoặc là B.
Ví dụ:
Với Bitmap hiển thị trên PictureBox (WinForm C#) hoặc WriteableBitmap hiển trị trên Image (WPF) thì nó hiển thị đúng.
Nhưng cũng với dữ lieu đó với QImage của QT hiển thị trên QLabel thì sẽ sai thứ tự màu. R và B sẽ đảo lộn.
=> Thực tế dữ lieu màu vẫn đúng nhưng thành phần hiển thị đã hiển thị sai.

1 Like

Mà khoan, các truy cập dữ lieu màu của cậu nó hơi lạ.
Mình truy cập qua con trỏ dữ lieu image.data chứ không getRow như thế kia.

Kiểu 1D:

int offset = y*image.step + x*image.channels;
pixelR = image.data[offset+2];
pixelG = image.data[offset+1];
pixelB = image.data[offset+0];

Kiểu 2D:

unsigned char ptr** = new unsigned char*[image.rows]
for(int y=0;y<image.rows;i++) ptr[y]=image.data + y*image.step;
int offset =  x*image.channels;
pixelR = ptr[y][offset+2];
pixelG =ptr[y][offset+1];
pixelB = ptr[y][offset+0];
1 Like

em tìm được 1 bài viết nói như thế này

the image on disk (png, jpg...) is in RGB order
the image in memory (mat, vec) is in BGR order.

và em làm theo thÌ đúng là ảnh trên đĩa là RGB, còn opencv là BGR. Vậy [0] là B, [1] là G, [2] là R

pixel[2] = (unsigned char) mat.getData(i, k + 0);  // r
pixel[1] = (unsigned char) mat.getData(i, k + 1);  // g
pixel[0] = (unsigned char) mat.getData(i, k + 2);  // b

em dùng imshow() của opencv ạ, vì em không làm gui

đưa code ko đúng chỗ nên mò ko ra là đúng rồi.

bản thân thằng cv::Mat ko có cần biết nó chứa RGB hay BGR, nó chỉ chứa dữ liệu thế thôi. Còn thằng cần quan tâm tới BGR hay RGB là ở thằng hiện nó ra lên màn hình, ở đây là cv::imshow

gu gồ "cv::imshow RGB or BGR" là ra ngay câu trả lời: https://stackoverflow.com/a/16413296/2683574

imshow() actually does not know the color space and will print images as BGR

thêm câu trả lời ở đây, chuyển rgbMat thành bgrMat: http://answers.opencv.org/question/33948/how-do-i-capture-process-and-output-an-image-in-rgb-not-bgr/?answer=33968#post-id-33968

thêm phát nữa: nếu data đọc được từ file ảnh lên là vector<uchar> thì có thể chuyển lẹ như sau, khỏi cần viết vòng lặp liếc gì hết:

cv::Mat getImage(void* data, int rows, int cols, int srcColorFlag, int cvtFlag)
{
    cv::Mat temp(rows, cols, srcColorFlag, data); //tạo cv::Mat thẳng từ data, khỏi cần vòng lặp gì hết
    cv::Mat ret;
    cv::cvtColor(temp, ret, cvtFlag); //nhờ cv::cvtColor convert sang BGR dùm
    return ret;
}

//...
//main
std::vector<uint8_t> rgbData(...); //rgb byte-order
int rows = ...;
int cols = ...;

auto image = getImage(rgbData.data(), rows, cols, CV_8UC3, cv::COLOR_RGB2BGR);
cv::imshow("...", image);

//hoặc đọc từ rgba
std::vector<uint8_t> rgbaData(...); //rgba byte-order
int rows = ...;
int cols = ...;

auto image = getImage(rgbaData.data(), rows, cols, CV_8UC4, cv::COLOR_RGBA2BGR);
cv::imshow("...", image);

cv::cvtColor
cv::ColorConversionCodes

nhớ xài cái namespace dùm cái, đọc vào cái code bên thớt kia thấy nào là Matrix nào là Mat, chả biết cái nào là tự định nghĩa cái nào là của opencv, khổ.

thằng opencv cũng tâm thần, đều là int flag mà chỗ thì đặt macro CV_..., chỗ thì xài namespace cv::..., vãi cả thư viện

2 Likes

cái getData() là getter của lớp Matrix em viết thôi ạ, để em chuyển data của Matrix sang public

e không dùng hàm gì trong thư viện opencv trừ cái imshow để hiển thị ảnh xem kết quả :grin:
đọc ảnh em tự code, nên thứ tự sau khi giải mã ảnh khác với thứ tự của opencv chút, e sửa được rồi ạ

xài sẵn mấy hàm của thư viện người ta luôn, đừng copy thủ công chi, 1 là chậm (copy từng byte), 2 là khổ vì phải đi debug xem vòng lặp for đúng sai chỗ nào.

2 Likes

Vấn đề có thể nó nằm ở chỗ dữ liệu ghi xuống đĩa có đúng định dạng hay không. Dữ liệu đọc lên cũng có đúng hay không. Bởi vì opencv có nhiều định dạng. Khi lưu định dạng khác nhau đó xuống đĩa thì định dạng dưới đĩa không phải lúc nào cũng là RGB. Đọc lên cũng vậy.

1 Like

data là mảng động 2d thì có dùng được kiểu đấy không anh unsinged char **data;
em cũng thấy người ra làm thế với mảng tĩnh, hoặc mảng 1d

tiện đây anh cho em hỏi, em muốn tách 1 vùng dựa vào đặc trưng màu của nó, thì dùng mô hình màu nào cho thích hợp hả ảnh
ví dụ

e muốn tách vùng có mã thẻ ra, nếu chuyển qua ảnh xám hoặc nhị phân làm thì rất cực, có thể tân dùng cái nền màu chứa mã thẻ

Tách dựa vào màu sắc nhiều khi có độ ổn định không cao trong trường hợp các màu lân cận sai lệch không nhiều với màu đối tượng cần tách.
Nếu muốn làm theo phương pháp đó thì phân tích tỷ lệ màu R,G,B. Tức là phân tích xem vùng mã thẻ nó có R,G,B trong ngưỡng nào, bao nhiêu % rồi lọc trong cái vùng cần tách có pixel nào phù hợp thì đánh dấu lại. Sau đó còn phải tìm mật độ các điểm đánh dấu nữa vì nó sẽ có cả những điểm ngoài vùng cũng được đánh dấu. Nếu điểm ngoài vùng nhiều quá thì cũng fail :smile:

Với đề bài trên mình nghĩ bạn dung các thuật toán matching có sẵn (TemplateMatching, ShapeMatching) để tìm được2 vị trí cái sọc xanh 2 bên. Từ vị trí sọc xanh tính ra vùng mã số vì vị trí của vùng mã thẻ so với 2 sọc xanh là cố định rồi.

1 Like

hình như ko @_@

mảng “động” 2 chiều T** dỏm lắm, xài mảng 1 chiều giả 2 chiều T* dễ copy hơn và copy lẹ hơn (ví dụ thông qua memcpy lẹ hơn copy từng dòng với T** rất nhiều). Nếu viết thư viện đọc file png thì nên đọc nó vào mảng 1 chiều width * height pixels luôn và trả về 1 cái struct gồm T* và 2 int width, height. Hầu như tất cả mọi thư viện đều hỗ trợ đọc dữ liệu từ mảng 1 chiều giả 2 chiều. Nếu viết code C++ thì đừng trả về T* mà trả về std::vector<T>.

2 Likes

e cũng đọc vào mảng 1d mà, nhưng kết quả trả về mảng 2d, vì còn liên quan đến xử lý

C++ có cho overload cái toán tử () mà, overload nó rồi xài thoải mái như mảng 2 chiều vậy:

//lưu ý T ko được là `bool`...
template <class T>
struct Array2d {
    std::vector<T> data; //xài vector thì khỏi cần quan tâm gì tới giải phóng bộ nhớ hay rule of 3, 5 gì hết
    int rows, cols;

    Array2d(int rows, int cols) : data(rows * cols), rows(rows), cols(cols) {}

    //m(r) -> con trỏ tới dòng thứ r
    T* operator()(int r) { return &data[r * cols]; }
    const T* operator()(int r)const { return &data[r * cols]; }

    //m(r, c) -> phần tử dòng r cột c, ví dụ auto x = m(r, c), hay m(r, c) = x
    T& operator()(int r, int c) { return data[r * cols + c]; }
    const T& operator()(int r, int c)const { return data[r * cols + c]; }
};
4 Likes

sao kiểu truy cập Mat của anh lạ lạ thế nhỉ

E tìm được các kiểu truy cập Mat trong opencv cũng như tốc độ của nó như sau

http://longstryder.com/2014/07/which-way-of-accessing-pixels-in-opencv-is-the-fastest/

Không có gì lạ cả. Một Mat sẽ có dữ liệu ảnh được trỏ bởi con trỏ data. Con trỏ này được public hiển nhiên truy cập qua nó hiệu suất xử lý cao nhất nhưng nó nguy hiểm. Trước kia mình hay dùng như vậy với MiplImage.

Cách truy cập qua ptr(int row) của bạn dùng thực chất là nó cũng lấy dữ liệu mà con trỏ data đang trỏ tới nhưng nó sẽ vòng vèo một chút. An toàn hơn và tất nhiên sẽ chậm hơn.

Trong link bạn đưa cũng đã nói như vậy.
Xem các member của cv::Mat và ý nghĩa của nó ở đây:
https://docs.opencv.org/3.1.0/d3/d63/classcv_1_1Mat.html

1 Like

Anh có thể xem lại các truy cập mat anh viết phía trên giúp em không, có gì đó sai sai :3 Em cũng đang cần tìm cách truy cập Mat nhanh nhất, để đẩy dữ liệu vào xem thử ảnh kết quả , vì phần xử lý ảnh, cấu trúc dữ liệu lưu trữ, đọc , ghi file ảnh em tự code, nhưng mỗi phần hiển thị k biết dùng cái gì hiển thị cho trực quan mà k cần dùng mấy lib bự bự như opencv :smile:

Kiểu 1D:

int offset = y*image.step + x*image.channels;
pixelR = image.data[offset+2];
pixelG = image.data[offset+1];
pixelB = image.data[offset+0];

Kiểu 2D:

unsigned char ptr** = new unsigned char*[image.rows]
for(int y=0;y<image.rows;i++) ptr[y]=image.data + y*image.step;
int offset = x*image.channels;
pixelR = ptr[y][offset+2];
pixelG =ptr[y][offset+1];
pixelB = ptr[y][offset+0];

Dấu . có thể là -> tùy vào cách bạn khai báo image là con trỏ hay không. Cái này là C++ quy định chắc là không vấn đề gì.
Code sẽ như thế này,. Chú ý là cách truy cập kiểu 2D sẽ chậm hơn 1D:

Kiểu 1D:

int offset = y*image.step + x*image.channels;
pixelR = image.data[offset+2];
pixelG = image.data[offset+1];
pixelB = image.data[offset+0];

Kiểu 2D:

unsigned char ptr** = new unsigned char*[image.rows];
for(int y=0;y<image.rows;y++) ptr[y]=image.data + y*image.step;
int offset = x*image.channels;
pixelR = ptr[y][offset+2];
pixelG =ptr[y][offset+1];
pixelB = ptr[y][offset+0];

Việc hiển thị thì có thể ghi xuống file (dung hàm cv::imwrite hoặc image->save(…)).
Hiển thị lên gui thì tùy vào bạn dung cái gì.

C++.NET hoặc C# WindowsForms thì dung PictureBox hiển thị Bitmap. Sẽ copy dữ lieu ảnh vào dữ lieu của Bitmap hoặc khai báo Bitmap có con trỏ dữ lieu trùng với con trỏ của Mat.

C# WPF thì sẽ dung Image hiển thị một WriteableBitmap. Cũng sẽ copy dữ lieu ảnh vào dữ lieu của WriteableBitmap.

C++ QT thì dung QLabel hiển thị một QPixmap. Khai báo một QImage để lưu dữ lieu ảnh. Sau đó chuyển QImage->QPixmap.

Cái khác mình không biết :smile:

1 Like

E code bằng c++. Vì em build 1 thư viện xla cơ bản, từ đọc ghi, lưu trữ, các thuật toán tiền xử lý. Phần nhận dạng dùng svm. Nên e test trên console. K có giao diện. Dùng qt thì cũng như opencv, cũng dùng thêm tool bên ngoài , mà e lại chưa học qt, nên em dùng opencv hiển thị vậy, tiện so sánh kết quả của thư viện chạy với kết quả mình tự gõ.
Em muốn hiển thị khi chạy chương trình cho trực quan

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?