我有一组图像文件,我希望将它们的颜色数量减少到64个。我该如何使用OpenCV实现这个目标?
我需要这么做是为了能够处理一个大小为64的图像直方图。我正在实现CBIR技术。
我的需求是将颜色量化到一个4位调色板中。
#include <iostream>
#include <vector>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
void colorReduce(cv::Mat& image, int div=64)
{
int nl = image.rows; // number of lines
int nc = image.cols * image.channels(); // number of elements per line
for (int j = 0; j < nl; j++)
{
// get the address of row j
uchar* data = image.ptr<uchar>(j);
for (int i = 0; i < nc; i++)
{
// process each pixel
data[i] = data[i] / div * div + div / 2;
}
}
}
int main(int argc, char* argv[])
{
// Load input image (colored, 3-channel, BGR)
cv::Mat input = cv::imread(argv[1]);
if (input.empty())
{
std::cout << "!!! Failed imread()" << std::endl;
return -1;
}
colorReduce(input);
cv::imshow("Color Reduction", input);
cv::imwrite("output.jpg", input);
cv::waitKey(0);
return 0;
}
Python中等价的代码如下所示: (感谢@eliezer-bernart)
import cv2
import numpy as np
input = cv2.imread('castle.jpg')
# colorReduce()
div = 64
quantized = input // div * div + div // 2
cv2.imwrite('output.jpg', quantized)
data[i] = data[i] / div * div + div / 2;
是如何工作的。 - ksyriumdiv
并结束计算,得到的图像会太暗。这种方法将量化后的颜色向上移动一些,以帮助保留一点原始颜色强度。至少从查看这个10年前的答案来看,这是我的理解。 - karlphillipdiv
,然后再乘以 div
,以获得小于像素值的 div
的倍数。然后,加上 div / 2
,以获得相邻的 div
倍数之间的中心位置。对每个 RGB 通道重复此过程,您将获得总共 256/div x 256/div x 256/div
种可能的颜色值。 - karlphillip你可以考虑使用K-means,但在这种情况下,它可能会非常慢。一种更好的方法是自己"手动"处理。假设你有一幅类型为CV_8UC3
的图像,即每个像素由来自0到255的3个RGB值(Vec3b
)表示。你可以将这256个值中的四个特定值"映射"到相应的颜色中,这将产生4 x 4 x 4
=64
种可能的颜色。
我曾经有一个数据集,需要确保黑色=黑色,白色=白色,并减少其他所有颜色的数量。这是我的做法(C++):
inline uchar reduceVal(const uchar val)
{
if (val < 64) return 0;
if (val < 128) return 64;
return 255;
}
void processColors(Mat& img)
{
uchar* pixelPtr = img.data;
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
const int pi = i*img.cols*3 + j*3;
pixelPtr[pi + 0] = reduceVal(pixelPtr[pi + 0]); // B
pixelPtr[pi + 1] = reduceVal(pixelPtr[pi + 1]); // G
pixelPtr[pi + 2] = reduceVal(pixelPtr[pi + 2]); // R
}
}
}
将[0,64)
变为0
,[64,128)
变为64
,[128,255)
变为255
,得到27
种颜色:
对我来说,这似乎很整洁、非常清晰,而且比其他答案中提到的任何解决方案都要快。
您还可以考虑将这些值减少到某个数字的倍数之一,比如:
inline uchar reduceVal(const uchar val)
{
if (val < 192) return uchar(val / 64.0 + 0.5) * 64;
return 255;
}
这将产生一组5个可能的值:{0、64、128、192、255}
,即125种颜色。
有许多方法可以实现。jeff7建议的方法是可以的,但一些缺点是:
我喜欢使用基于最高有效位的算法来使用RGB颜色并将其转换为64色图像。如果您正在使用C/OpenCV,则可以使用下面的函数之类的东西。
如果您正在使用灰度图像,则建议使用OpenCV 2.3的LUT()函数,因为它速度更快。有一个关于如何使用LUT减少颜色数量的教程。请参见:教程:如何扫描图像,查找表...但是,如果您正在使用RGB图像,则我发现它更加复杂。
void reduceTo64Colors(IplImage *img, IplImage *img_quant) {
int i,j;
int height = img->height;
int width = img->width;
int step = img->widthStep;
uchar *data = (uchar *)img->imageData;
int step2 = img_quant->widthStep;
uchar *data2 = (uchar *)img_quant->imageData;
for (i = 0; i < height ; i++) {
for (j = 0; j < width; j++) {
// operator XXXXXXXX & 11000000 equivalent to XXXXXXXX AND 11000000 (=192)
// operator 01000000 >> 2 is a 2-bit shift to the right = 00010000
uchar C1 = (data[i*step+j*3+0] & 192)>>2;
uchar C2 = (data[i*step+j*3+1] & 192)>>4;
uchar C3 = (data[i*step+j*3+2] & 192)>>6;
data2[i*step2+j] = C1 | C2 | C3; // merges the 2 MSB of each channel
}
}
}
void
。 - karlphillipcv2.kmeans
的Python颜色量化实现。其思想是尽可能地减少图像中不同颜色的数量,同时尽可能地保留图像的颜色外观。这是结果:
输入 ->
输出
代码
import cv2
import numpy as np
def kmeans_color_quantization(image, clusters=8, rounds=1):
h, w = image.shape[:2]
samples = np.zeros([h*w,3], dtype=np.float32)
count = 0
for x in range(h):
for y in range(w):
samples[count] = image[x][y]
count += 1
compactness, labels, centers = cv2.kmeans(samples,
clusters,
None,
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001),
rounds,
cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8(centers)
res = centers[labels.flatten()]
return res.reshape((image.shape))
image = cv2.imread('1.jpg')
result = kmeans_color_quantization(image, clusters=8)
cv2.imshow('result', result)
cv2.waitKey()
#include "include\opencv\cv.h"
#include "include\opencv\highgui.h"
// quantize the image to numBits
cv::Mat quantizeImage(const cv::Mat& inImage, int numBits)
{
cv::Mat retImage = inImage.clone();
uchar maskBit = 0xFF;
// keep numBits as 1 and (8 - numBits) would be all 0 towards the right
maskBit = maskBit << (8 - numBits);
for(int j = 0; j < retImage.rows; j++)
for(int i = 0; i < retImage.cols; i++)
{
cv::Vec3b valVec = retImage.at<cv::Vec3b>(j, i);
valVec[0] = valVec[0] & maskBit;
valVec[1] = valVec[1] & maskBit;
valVec[2] = valVec[2] & maskBit;
retImage.at<cv::Vec3b>(j, i) = valVec;
}
return retImage;
}
int main ()
{
cv::Mat inImage;
inImage = cv::imread("testImage.jpg");
char buffer[30];
for(int i = 1; i <= 8; i++)
{
cv::Mat quantizedImage = quantizeImage(inImage, i);
sprintf(buffer, "%d Bit Image", i);
cv::imshow(buffer, quantizedImage);
sprintf(buffer, "%d Bit Image.png", i);
cv::imwrite(buffer, quantizedImage);
}
cv::waitKey(0);
return 0;
}
下面是在上述函数调用中使用的图片:
每个RGB通道量化为2位(总共64种颜色):
每个通道量化为3位:
每个通道量化为4位...
capImage &= cv::Scalar(0b11000000, 0b11000000, 0b11000000);
capImage &= 0b11111100;
使用适当的位掩码进行简单的按位与运算即可解决问题。
对于64种颜色,Python中的方法为:
img = img & int("11000000", 2)
import numpy as np
import cv2 as cv
def is_cube(n):
cbrt = np.cbrt(n)
return cbrt ** 3 == n, int(cbrt)
def reduce_color_space(img, n_colors=64):
n_valid, cbrt = is_cube(n_colors)
if not n_valid:
print("n_colors should be a perfect cube")
return
n_bits = int(np.log2(cbrt))
if n_bits > 8:
print("Can't generate more colors")
return
bitmask = int(f"{'1' * n_bits}{'0' * (8 - n_bits)}", 2)
return img & bitmask
img = cv.imread("image.png")
cv.imshow("orig", img)
cv.imshow("reduced", reduce_color_space(img))
cv.waitKey(0)
假设您想为所有图像使用相同的64种颜色(即调色板不针对每个图像进行优化),那么我可以想到至少有几种选择:
1)将其转换为Lab或YCrCb颜色空间,并使用N位用于亮度和M位用于每个颜色通道进行量化,其中N应大于M。
2)在所有训练图像上计算颜色值的3D直方图,然后选择具有最大bin值的64种颜色。通过将每个像素分配给来自训练集中最接近的bin的颜色来量化您的图像。
方法1是最通用且最易于实现的方法,而方法2可以更好地适应您的特定数据集。
更新: 例如,32种颜色为5位,因此将3位分配给亮度通道和1位分配给每个颜色通道。要进行量化,请将亮度通道除以2 ^ 8 / 2 ^ 3 = 32,将每个颜色通道除以2 ^ 8 / 2 ^ 1 = 128。现在只有8种不同的亮度值和每种2种不同的颜色通道。通过位移或数学运算将这些值重新组合成单个整数(量化的颜色值=亮度* 4 + color1 * 2 + color2);
img = numpy.multiply(img//32, 32)