正如评论中提到的,答案是使用感知色彩空间,但我想我会拼凑一个视觉示例,说明边缘检测在两个色彩空间中的行为方式。(代码在最后。)在这两种情况下,都是对3通道彩色图像进行Sobel边缘检测,然后将结果展平为灰度。
RGB空间:

L*a*b 空间(图像是对数的,因为第三个字母的边缘比第一个字母的边缘更重要,第一个字母的边缘比第二个字母的边缘更重要):

OpenCV C++ 代码:
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "iostream"
using namespace cv;
using namespace std;
void show(const char *name, Mat &img, int dolog=0)
{
double minVal, maxVal;
minMaxLoc(img, &minVal, &maxVal);
cout << name << " " << "minVal : " << minVal << endl << "maxVal : " << maxVal << endl;
Mat draw;
if(dolog) {
Mat shifted, tmp;
add(img, minVal, shifted);
log(shifted, tmp);
minMaxLoc(tmp, &minVal, &maxVal);
tmp.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
} else {
img.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
}
namedWindow(name, CV_WINDOW_AUTOSIZE);
imshow(name, draw);
imwrite(name, draw);
}
int main( )
{
Mat src;
src = imread("AAABBB.png", CV_LOAD_IMAGE_COLOR);
namedWindow( "Original image", CV_WINDOW_AUTOSIZE );
imshow( "Original image", src );
Mat lab, gray;
cvtColor(src, lab, CV_BGR2Lab);
Mat sobel_lab, sobel_bgr;
Sobel(lab, sobel_lab, CV_32F, 1, 0);
Sobel(src, sobel_bgr, CV_32F, 1, 0);
Mat bgr_sobel_lab, gray_sobel_lab;
cvtColor(sobel_lab, bgr_sobel_lab, CV_Lab2BGR);
show("lab->bgr edges.png", bgr_sobel_lab, 1);
cvtColor(bgr_sobel_lab, gray_sobel_lab, CV_BGR2GRAY);
Mat gray_sobel_bgr;
cvtColor(sobel_bgr, gray_sobel_bgr, CV_BGR2GRAY);
show("lab edges.png", gray_sobel_lab, 1);
show("bgr edges.png", gray_sobel_bgr);
waitKey(0);
return 0;
}