Converting Processing code to POVray - processing

I am trying to convert a bit of code from Processing to POVray format. Here's the original:
http://www.openprocessing.org/sketch/17344
Here's the POVray version I created:
#declare TWO_PI = 2 * pi;
#declare iNx = 32; // the number of "horizontal" lines per charge, not taking into account iXmin
#declare iNy = 32; // the number of "vertical" lines per charge
#declare iXmin = 24; // size of the empty hole for each charge, measured in "horizontal" lines
#declare iSym = 5; // number of charges arranged around the origin
#declare iSx = 2;
#declare iSy = 2;
#declare fMouseY = 300; // need to get rid of this
#declare bLines = true; // show lines connecting the points
#declare bInside = true; // are the curved lines inside or outside of the reference shape? or, are the charges inside or outside the reference shape?
#macro mag(fX, fY)
dist(fX, fY, 0, 0)
#end
#macro dist(fX1, fY1, fX2, fY2)
sqrt(pow(fX2 - fX1, 2) + pow(fY2 - fY1, 2))
#end
#macro calc()
#local fY1 = 0;
#local fX1 = 0;
#local fX2 = exp(fX1) * cos(fY1);
#local fY2 = exp(fX1) * sin(fY1) + fMouseY;
#if (bInside)
#local fD = pow(mag(fX2, fY2), 1/iSym);
#else
#local fD = pow(mag(fX2, fY2), -1/iSym);
#end
#local fArg = atan2(fX2, fY2) * -1/iSym;
#local fX3 = fD * cos(fArg)/TWO_PI;
#local fY3 = fD * sin(fArg)/TWO_PI;
vlength(<fX3, fY3, 0,>) // return
#end
#macro draw()
#for (i, 0, iNy - 1)
#local fY = i * TWO_PI/iNy;
#for (j, iXmin * iSx, iNx * iSx - 1)
#local fX1 = j * TWO_PI/iNx/iSx;
#local fX2 = (j + 1) * TWO_PI/iNx/iSx;
lines(fn(fX1, fY), fn(fX2, fY))
#end
#end
#for (i, iXmin, iNx)
#local fX = i * TWO_PI/iNx;
#for (j, 0, iNy * iSy - 1)
#local fY1 = j * TWO_PI/iNy/iSy;
#local fY2 = (j + 1) * TWO_PI/iNy/iSy;
lines(fn(fX, fY1), fn(fX, fY2))
#end
#end
#end
#macro lines(aP1, aP2)
#for (i, 0, iSym - 1)
#local fX1 = aP1[i][0];
#local fY1 = aP1[i][2];
#local fX2 = aP2[i][0];
#local fY2 = aP2[i][3];
sphere
{
<fX1, fY1, 0,>/calc(),
0.005
pigment {color rgb x}
}
#if (bLines)
cylinder
{
<fX1, fY1, 0,>/calc(),
<fX2, fY2, 0,>/calc(),
0.005
pigment {color rgb x}
}
#end
#end
#end
// this is where the magic happens ...
#macro fn(fX, fY)
#local aP = array[iSym][2];
#local fX1 = exp(fX) * cos(fY);
#local fY1 = exp(fX) * sin(fY) - fMouseY;
#if (bInside)
#local fD = pow(mag(fX1, fY1), 1/iSym);
#else
#local fD = pow(mag(fX1, fY1), -1/iSym);
#end
#local fArg = atan2(fX1, fY1) * -1/iSym;
#for (i, 0, iSym - 1)
#local fX2 = fD * cos(fArg + i * TWO_PI/iSym);
#local fY2 = fD * sin(fArg + i * TWO_PI/iSym);
#local aP[i][0] = fX2/TWO_PI;
#local aP[i][4] = fY2/TWO_PI;
#end
aP //return
#end
draw()
However, the result is broken in one regard. There are now lines criss-crossing the image when they should instead should be following smooth curves. See:
I've looked and looked and can't find anything I did wrong. My suspicion is that some of the trigonometric functions such as atan2 behave differently in the two languages. Perhaps one language can't handle input larger than 2 * pi?
Is that possible? Otherwise I have no clue and would appreciate some help.

Related

embree detecting a false contact between a triangulo and a segment

I have a triangle and a segment which, according to embree, they are colliding. However, when I draw this elements, it is clear they don't collide.
In the image, the dots on the triangle and the segment are the intersection points, computed using the barycentric coordinates and the intersection distance given by embree.
I have also tested with CGAL if the elements collide, and CGAL doesn't detect any intersection.
I have absolutely no clue on what might be happening. I have been able to create a minimal reproducible example which shows the behavior I am describing
#include <embree3/rtcore_common.h>
#include <tuple>
#include <embree3/rtcore_device.h>
#include <embree3/rtcore_geometry.h>
#include <embree3/rtcore_ray.h>
#include <embree3/rtcore_scene.h>
#include <iostream>
// To get the points exactly as we saw them on the error
float dec(int x) {
struct U {
union {
int i;
float f;
};
};
U u;
u.i = x;
return u.f;
}
int main() {
// Data to reproduce the issue
auto [v0x, v0y, v0z] =
std::make_tuple(dec(0x3e6a2c13), dec(0x3fad24ce), dec(0x3ecc9e88));
auto [v1x, v1y, v1z] =
std::make_tuple(dec(0x3e6e35dd), dec(0x3fad7b6a), dec(0x3eca0623));
auto [v2x, v2y, v2z] =
std::make_tuple(dec(0x3e692c70), dec(0x3fad7b6a), dec(0x3eca0623));
RTCRay ray;
ray.org_x = dec(0x3e708b43);
ray.org_y = dec(0x3fad42a5);
ray.org_z = dec(0x3ecbb9a0);
ray.tfar = dec(0x3b9631f5);
ray.dir_x = dec(0x3f137064);
ray.dir_y = dec(0x3ec18586);
ray.dir_z = dec(0xbf399122);
ray.tnear = 0.0F;
ray.mask = 0;
ray.time = 0.0F;
ray.flags = 0;
// Create device and scene
auto device = rtcNewDevice("");
auto scene = rtcNewScene(device);
rtcSetSceneFlags(scene, RTC_SCENE_FLAG_DYNAMIC |
RTC_SCENE_FLAG_CONTEXT_FILTER_FUNCTION);
rtcSetSceneBuildQuality(scene, RTC_BUILD_QUALITY_LOW);
// Create geometry
auto geometry = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE);
rtcSetGeometryBuildQuality(geometry, RTC_BUILD_QUALITY_REFIT);
auto geomId = rtcAttachGeometry(scene, geometry);
auto *vb = static_cast<float *>(
rtcSetNewGeometryBuffer(geometry, RTC_BUFFER_TYPE_VERTEX, 0,
RTC_FORMAT_FLOAT3, 4 * sizeof(float), 12));
vb[4 * 0 + 0] = v0x;
vb[4 * 0 + 1] = v0y;
vb[4 * 0 + 2] = v0z;
vb[4 * 1 + 0] = v1x;
vb[4 * 1 + 1] = v1y;
vb[4 * 1 + 2] = v1z;
vb[4 * 2 + 0] = v2x;
vb[4 * 2 + 1] = v2y;
vb[4 * 2 + 2] = v2z;
auto *ib = static_cast<int *>(
rtcSetNewGeometryBuffer(geometry, RTC_BUFFER_TYPE_INDEX, 0,
RTC_FORMAT_UINT3, 3 * sizeof(int), 3));
ib[0] = 0;
ib[1] = 1;
ib[2] = 2;
rtcCommitGeometry(geometry);
rtcCommitScene(scene);
// Perform the query
RTCIntersectContext context{};
rtcInitIntersectContext(&context);
context.flags = RTC_INTERSECT_CONTEXT_FLAG_INCOHERENT;
rtcOccluded1(scene, &context, &ray);
// Same behavior with rtcIntersect1
std::cout << ray.tfar << std::endl; // -inf here means an intersection has been found
}
I have uploaded the example to gitlab in case someone wats to test it (https://gitlab.com/juanjo.casafranca/embree_issue)

How can I read/transform the range images of the stanford bunny .ply-files?

I want to read the not reconstructed data from the Stanford Bunny. The point data is stored as several range images, which have to be transformed to be combined to one big point cloud, like written in the README:
These data files were obtained with a Cyberware 3030MS optical
triangulation scanner. They are stored as range images in the "ply"
format. The ".conf" file contains the transformations required to
bring each range image into a single coordinate system.
This is the .conf-file:
camera -0.0172 -0.0936 -0.734 -0.0461723 0.970603 -0.235889 0.0124573
bmesh bun000.ply 0 0 0 0 0 0 1
bmesh bun045.ply -0.0520211 -0.000383981 -0.0109223 0.00548449 -0.294635 -0.0038555 0.955586
bmesh bun090.ply 2.20761e-05 -3.34606e-05 -7.20881e-05 0.000335889 -0.708202 0.000602459 0.706009
bmesh bun180.ply 0.000116991 2.47732e-05 -4.6283e-05 -0.00215148 0.999996 -0.0015001 0.000892527
bmesh bun270.ply 0.000130273 1.58623e-05 0.000406764 0.000462632 0.707006 -0.00333301 0.7072
bmesh top2.ply -0.0530127 0.138516 0.0990356 0.908911 -0.0569874 0.154429 0.383126
bmesh top3.ply -0.0277373 0.0583887 -0.0796939 0.0598923 0.670467 0.68082 -0.28874
bmesh bun315.ply -0.00646017 -1.36122e-05 -0.0129064 0.00449209 0.38422 -0.00976512 0.923179
bmesh chin.ply 0.00435102 0.0882863 -0.108853 -0.441019 0.213083 0.00705734 0.871807
bmesh ear_back.ply -0.0829384 0.0353082 0.0711536 0.111743 0.925689 -0.215443 -0.290169
For each range image seven values are stored. But I do not know, what information can be obtained from these values.
I guess that three of them will contain some information about the translation and maybe three contain information about the rotation. But I didn't find something about the order of these values and how to transform the values to get one point cloud.
The wiki page doesn't handle with range images and I found nothing more at the Stanford pages. They just talk about, that the method of Turk94 is used to scan this data set, but the method has no information about the transformations needed. (Or I was not able to get the information out of this paper.)
Does anybody know how to read these values correctly? Why is there a transformation for the camera position? Is this just a good initial value to view the whole point cloud?
Thanks for your help.
EDIT:
Ok. At this point, I already tried to read the data and to correctly transform them, but everything did not work. I use the boost library to handle with the quaternions
Here is my code for it:
boost::math::quaternion<double> translation, quaternionRotation;
//Get Transformation
translation = boost::math::quaternion<double>(0.0, lineData[2].toDouble(), lineData[3].toDouble(), lineData[4].toDouble());
quaternionRotation = boost::math::quaternion<double>(lineData[5].toDouble(),lineData[6].toDouble(),lineData[7].toDouble(),lineData[8].toDouble());
//do some file related stuff
//...
//for each line: read the point data and transform it and store the point in a data array
pointData[j].x = stringPointData[0].toDouble();
pointData[j].y = stringPointData[1].toDouble();
pointData[j].z = stringPointData[2].toDouble();
tmpQuat = boost::math::quaternion<double> (0.0,pointData[j].x,pointData[j].y,pointData[j].z);
//first translation
tmpQuat += translation;
//then quaternion rotation
tmpQuat = (quaternionRotation * (tmpQuat) * boost::math::conj(quaternionRotation));
//read the data from quaternion to a usual type
pointData[j].x = tmpQuat.R_component_2();
pointData[j].y = tmpQuat.R_component_3();
pointData[j].z = tmpQuat.R_component_4();
I assume that the first component of the quaternion is the w component and the others refers to x, y andz like in equation 2 from here. If necessary I can provide the screenshots of the false transformations.
EDIT: It is written in the source code of zipper in the file zipper.c, that the 7 values are saved as followed:
transX transY transZ quatX quatY quatZ quatW
The quaternion is then transformed into a rotation matrix and then the rotation is performed with this new matrix. But even with this information, I am not able to transform it correctly. To test it, I implemented the function quat_to_mat() from zipper in my project:
glm::dmat4 cPlyObjectLoader::quat_to_mat(boost::math::quaternion<double> quat) const
{
float s;
float xs,ys,zs;
float wx,wy,wz;
float xx,xy,xz;
float yy,yz,zz;
glm::dmat4 mat(1.0);
s = 2 / (quat.R_component_2()*quat.R_component_2() +
quat.R_component_3()*quat.R_component_3() +
quat.R_component_4()*quat.R_component_4() +
quat.R_component_1()*quat.R_component_1());
xs = quat.R_component_2() * s;
ys = quat.R_component_3() * s;
zs = quat.R_component_4() * s;
wx = quat.R_component_1() * xs;
wy = quat.R_component_1() * ys;
wz = quat.R_component_1() * zs;
xx = quat.R_component_2() * xs;
xy = quat.R_component_2() * ys;
xz = quat.R_component_2() * zs;
yy = quat.R_component_3() * ys;
yz = quat.R_component_3() * zs;
zz = quat.R_component_4() * zs;
mat[0][0] = 1 - (yy + zz);
mat[0][1] = xy - wz;
mat[0][2] = xz + wy;
mat[0][3] = 0;
mat[1][0] = xy + wz;
mat[1][1] = 1 - (xx + zz);
mat[1][2] = yz - wx;
mat[1][3] = 0;
mat[2][0] = xz - wy;
mat[2][1] = yz + wx;
mat[2][2] = 1 - (xx + yy);
mat[2][3] = 0;
mat[3][0] = 0;
mat[3][1] = 0;
mat[3][2] = 0;
mat[3][3] = 1;
return mat;
}
Now I am doing the translation and rotation with a vector and this matrix:
quaternionRotation = boost::math::quaternion<double>(lineData[8].toDouble(),lineData[5].toDouble(),lineData[6].toDouble(),lineData[7].toDouble());
rotationMat = this->quat_to_mat(quaternionRotation);
translationVec = glm::dvec4(lineData[2].toDouble(), lineData[3].toDouble(), lineData[4].toDouble(),0.0);
//same stuff as above
//...
glm::dvec4 curPoint = glm::dvec4(pointData[j].x,pointData[j].y,pointData[j].z,1.0);
curPoint += translationVec;
curPoint = rotationMat*curPoint;
The result is different to my quaternion rotation (Why? It should be the same.), but not correct.
Debug information:
the input of all transformations is correct
the input of all points is correct
As i read from stanford 3d scan
For all the Stanford models, alignment was done using a modified ICP
algorithm, as described in this paper. These alignments are stored in
".conf" files, which list each range image in the model along with a
translation and a quaternion rotation.
Here is the link to "this paper"
Edit: The two methods are called zippering and volmetric merging
As Ello mentioned, it is written at the stanford 3D repo:
For all the Stanford models, alignment was done using a modified ICP algorithm, as described in this paper. These alignments are stored in ".conf" files, which list each range image in the model along with a translation and a quaternion rotation.
But that is not enough to understand everything of this data file.
It is correct, that the first line:
camera -0.0172 -0.0936 -0.734 -0.0461723 0.970603 -0.235889 0.0124573
stores a good initial camera position and every other line starting with bmesh refers to a .ply-file, which stores a ranged image.
The transformation values are stored as followed:
transX transY transZ quatX quatY quatZ quatW
where trans... refers to a translation value and quat... refers to a value of the quaternion. Currently, I do not know, why it doesn't work with the quaternion rotation by itself, but by transforming it into a rotation matrix with the code of zipper the transformation is correct. Be aware, that the translation is stored first, but to get a correct transformation the rotation has to be done at the beginning and the translation afterwards.
My code snippet to read the files and transform it, is the following:
boost::math::quaternion<double> translation, quaternionRotation;
//Get Transformation
translationVec = glm::dvec4(lineData[2].toDouble(), lineData[3].toDouble(), lineData[4].toDouble(),0.0);
quaternionRotation = boost::math::quaternion<double>(lineData[8].toDouble(),lineData[5].toDouble(),lineData[6].toDouble(),lineData[7].toDouble());
//calculate the unit quaternion
double magnitude = std::sqrt(
quaternionRotation.R_component_1()*quaternionRotation.R_component_1()+
quaternionRotation.R_component_2()*quaternionRotation.R_component_2()+
quaternionRotation.R_component_3()*quaternionRotation.R_component_3()+
quaternionRotation.R_component_4()*quaternionRotation.R_component_4());
quaternionRotation /= magnitude;
rotationMat = this->quat_to_mat(quaternionRotation);
//do some file related stuff
//...
//for each line: read the point data and transform it and store the point in a data array
pointData[j].x = stringPointData[0].toDouble();
pointData[j].y = stringPointData[1].toDouble();
pointData[j].z = stringPointData[2].toDouble();
//transform the curren point
glm::dvec4 curPoint = glm::dvec4(pointData[j].x,pointData[j].y,pointData[j].z,1.0);
//first rotation
curPoint = rotationMat*curPoint;
//then translation
curPoint += translationVec;
//store the data in a data array
pointData[j].x = curPoint.x;
pointData[j].y = curPoint.y;
pointData[j].z = curPoint.z;
I know, that it's not the best one, but it works. Feel free to optimize it by yourself.
Here is the file converter that I wrote. It will assemble all the scans into a single file, one point per line. It supports different file formats (including Stanford .conf files).
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265
#endif
class LineInput {
public:
LineInput(const std::string& filename) {
F_ = fopen(filename.c_str(), "r" ) ;
ok_ = (F_ != 0) ;
}
~LineInput() {
if(F_ != 0) {
fclose(F_); F_ = 0 ;
}
}
bool OK() const { return ok_ ; }
bool eof() const { return feof(F_) ; }
bool get_line() {
line_[0] = '\0' ;
// Skip the empty lines
while(!isprint(line_[0])) {
if(fgets(line_, MAX_LINE_LEN, F_) == 0) {
return false ;
}
}
// If the line ends with a backslash, append
// the next line to the current line.
bool check_multiline = true ;
int total_length = MAX_LINE_LEN ;
char* ptr = line_ ;
while(check_multiline) {
int L = strlen(ptr) ;
total_length -= L ;
ptr = ptr + L - 2;
if(*ptr == '\\' && total_length > 0) {
*ptr = ' ' ;
ptr++ ;
fgets(ptr, total_length, F_) ;
} else {
check_multiline = false ;
}
}
if(total_length < 0) {
std::cerr
<< "MultiLine longer than "
<< MAX_LINE_LEN << " bytes" << std::endl ;
}
return true ;
}
int nb_fields() const { return field_.size() ; }
char* field(int i) { return field_[i] ; }
int field_as_int(int i) {
int result ;
ok_ = ok_ && (sscanf(field(i), "%d", &result) == 1) ;
return result ;
}
double field_as_double(int i) {
double result ;
ok_ = ok_ && (sscanf(field(i), "%lf", &result) == 1) ;
return result ;
}
bool field_matches(int i, const char* s) {
return !strcmp(field(i), s) ;
}
void get_fields(const char* separators=" \t\r\n") {
field_.resize(0) ;
char* tok = strtok(line_,separators) ;
while(tok != 0) {
field_.push_back(tok) ;
tok = strtok(0,separators) ;
}
}
private:
enum { MAX_LINE_LEN = 65535 } ;
FILE* F_ ;
char line_[MAX_LINE_LEN] ;
std::vector<char*> field_ ;
bool ok_ ;
} ;
std::string to_string(int x, int mindigits) {
char buff[100] ;
sprintf(buff, "%03d", x) ;
return std::string(buff) ;
}
double M[4][4] ;
void transform(double* xyz) {
double xyzw[4] ;
for(unsigned int c=0; c<4; c++) {
xyzw[c] = M[3][c] ;
}
for(unsigned int j=0; j<4; j++) {
for(unsigned int i=0; i<3; i++) {
xyzw[j] += M[i][j] * xyz[i] ;
}
}
for(unsigned int c=0; c<3; c++) {
xyz[c] = xyzw[c] / xyzw[3] ;
}
}
bool read_frames_file(int no) {
std::string filename = "scan" + to_string(no,3) + ".frames" ;
std::cerr << "Reading frames from:" << filename << std::endl ;
LineInput in(filename) ;
if(!in.OK()) {
std::cerr << " ... not found" << std::endl ;
return false ;
}
while(!in.eof() && in.get_line()) {
in.get_fields() ;
if(in.nb_fields() == 17) {
int f = 0 ;
for(unsigned int i=0; i<4; i++) {
for(unsigned int j=0; j<4; j++) {
M[i][j] = in.field_as_double(f) ; f++ ;
}
}
}
}
return true ;
}
bool read_pose_file(int no) {
std::string filename = "scan" + to_string(no,3) + ".pose" ;
std::cerr << "Reading pose from:" << filename << std::endl ;
LineInput in(filename) ;
if(!in.OK()) {
std::cerr << " ... not found" << std::endl ;
return false ;
}
double xyz[3] ;
double euler[3] ;
in.get_line() ;
in.get_fields() ;
xyz[0] = in.field_as_double(0) ;
xyz[1] = in.field_as_double(1) ;
xyz[2] = in.field_as_double(2) ;
in.get_line() ;
in.get_fields() ;
euler[0] = in.field_as_double(0) * M_PI / 180.0 ;
euler[1] = in.field_as_double(1) * M_PI / 180.0 ;
euler[2] = in.field_as_double(2) * M_PI / 180.0 ;
double sx = sin(euler[0]);
double cx = cos(euler[0]);
double sy = sin(euler[1]);
double cy = cos(euler[1]);
double sz = sin(euler[2]);
double cz = cos(euler[2]);
M[0][0] = cy*cz;
M[0][1] = sx*sy*cz + cx*sz;
M[0][2] = -cx*sy*cz + sx*sz;
M[0][3] = 0.0;
M[1][0] = -cy*sz;
M[1][1] = -sx*sy*sz + cx*cz;
M[1][2] = cx*sy*sz + sx*cz;
M[1][3] = 0.0;
M[2][0] = sy;
M[2][1] = -sx*cy;
M[2][2] = cx*cy;
M[2][3] = 0.0;
M[3][0] = xyz[0];
M[3][1] = xyz[1];
M[3][2] = xyz[2];
M[3][3] = 1.0;
return true ;
}
void setup_transform_from_translation_and_quaternion(
double Tx, double Ty, double Tz,
double Qx, double Qy, double Qz, double Qw
) {
/* for unit q, just set s = 2 or set xs = Qx + Qx, etc. */
double s = 2.0 / (Qx*Qx + Qy*Qy + Qz*Qz + Qw*Qw);
double xs = Qx * s;
double ys = Qy * s;
double zs = Qz * s;
double wx = Qw * xs;
double wy = Qw * ys;
double wz = Qw * zs;
double xx = Qx * xs;
double xy = Qx * ys;
double xz = Qx * zs;
double yy = Qy * ys;
double yz = Qy * zs;
double zz = Qz * zs;
M[0][0] = 1.0 - (yy + zz);
M[0][1] = xy - wz;
M[0][2] = xz + wy;
M[0][3] = 0.0;
M[1][0] = xy + wz;
M[1][1] = 1 - (xx + zz);
M[1][2] = yz - wx;
M[1][3] = 0.0;
M[2][0] = xz - wy;
M[2][1] = yz + wx;
M[2][2] = 1 - (xx + yy);
M[2][3] = 0.0;
M[3][0] = Tx;
M[3][1] = Ty;
M[3][2] = Tz;
M[3][3] = 1.0;
}
bool read_points_file(int no) {
std::string filename = "scan" + to_string(no,3) + ".3d" ;
std::cerr << "Reading points from:" << filename << std::endl ;
LineInput in(filename) ;
if(!in.OK()) {
std::cerr << " ... not found" << std::endl ;
return false ;
}
while(!in.eof() && in.get_line()) {
in.get_fields() ;
double xyz[3] ;
if(in.nb_fields() >= 3) {
for(unsigned int c=0; c<3; c++) {
xyz[c] = in.field_as_double(c) ;
}
transform(xyz) ;
printf("%f %f %f\n",xyz[0],xyz[1],xyz[2]) ;
}
}
return true ;
}
/* only works for ASCII PLY files */
void read_ply_file(char* filename) {
std::cerr << "Reading points from:" << filename << std::endl;
LineInput in(filename) ;
if(!in.OK()) {
std::cerr << filename << ": could not open" << std::endl ;
return;
}
bool reading_vertices = false;
int nb_vertices = 0 ;
int nb_read_vertices = 0 ;
while(!in.eof() && in.get_line()) {
in.get_fields();
if(reading_vertices) {
double xyz[3] ;
for(unsigned int c=0; c<3; c++) {
xyz[c] = in.field_as_double(c) ;
}
transform(xyz) ;
printf("%f %f %f\n",xyz[0],xyz[1],xyz[2]) ;
++nb_read_vertices;
if(nb_read_vertices == nb_vertices) {
return;
}
} else if(
in.field_matches(0,"element") &&
in.field_matches(1,"vertex")
) {
nb_vertices = in.field_as_int(2);
} else if(in.field_matches(0,"end_header")) {
reading_vertices = true;
}
}
}
/* For Stanford scanning repository */
void read_conf_file(char* filename) {
LineInput in(filename) ;
if(!in.OK()) {
std::cerr << filename << ": could not open" << std::endl ;
return;
}
while(!in.eof() && in.get_line()) {
in.get_fields();
if(in.nb_fields() == 0) { continue ; }
if(in.field_matches(0,"bmesh")) {
char* filename = in.field(1);
// Translation vector
double Tx = in.field_as_double(2);
double Ty = in.field_as_double(3);
double Tz = in.field_as_double(4);
/// Quaternion
double Qx = in.field_as_double(5);
double Qy = in.field_as_double(6);
double Qz = in.field_as_double(7);
double Qw = in.field_as_double(8);
setup_transform_from_translation_and_quaternion(Tx,Ty,Tz,Qx,Qy,Qz,Qw);
read_ply_file(filename);
}
}
}
int main(int argc, char** argv) {
if(argc != 2) { return -1 ; }
if(strstr(argv[1],".conf")) {
read_conf_file(argv[1]);
} else {
int max_i = atoi(argv[1]) ;
for(int i=0; i<=max_i; i++) {
if(!read_frames_file(i)) {
read_pose_file(i) ;
}
read_points_file(i) ;
}
}
return 0 ;
}
Okay so here is my solution since none of the above worked for me (note this is in python using blender's bpy). It seems that I need to transpose the rotation part of my 4x4 transformation matrix (note I am using a standard way to convert quaternion to rotation matrix and not the one from zipper). Also note since I am using blender when importing or using any model it only stores the models local coordinates relative to the objects world transformation so you do not have to do this point = objWorld * point, it is blender specific.
#loop
for meshName, transform in zip(plyFile, transformations):
#Build Quaternion
#transform structure [x, y, z, qx, qy, qz, qw]
Rt = mathutils.Quaternion((transform[6], transform[3], transform[4], transform[5])).to_matrix().to_4x4()
Rt.normalize()
Rt.transpose()
Rt[0][3] = transform[0]
Rt[1][3] = transform[1]
Rt[2][3] = transform[2]
bpy.ops.object.select_all(action='DESELECT')
#import the ply mesh into blender
bpy.ops.import_mesh.ply(filepath=baseDir + meshName)
#get the ply object
obj = bpy.context.object
#get objects world matrix
objWorld = obj.matrix_world
for index in range(len(obj.data.vertices)):
#get local point
point = mathutils.Vector([obj.data.vertices[index].co[0],obj.data.vertices[index].co[1], obj.data.vertices[index].co[2], 1.])
#convert local point to world
point = objWorld * point
#apply ply transformation
point = Rt * point
#update the point in the mesh
obj.data.vertices[index].co[0] = point[0]
obj.data.vertices[index].co[1] = point[1]
obj.data.vertices[index].co[2] = point[2]
#all vertex positions should be updated correctly
As mentioned in other answers, the Stanford 3D repository gives some info about the data organization in the '.conf' files but, the transformation for the bunny model were not working properly when using the quaternion data provided.
I was also stuck in this registration problem for the bunny model, and based on my tests I have some extra considerations to add up. When applying the transformation - rotations to be more specific - I have realized that quaternion values were not rotating the cloud in the correct direction but, when using the corresponding Euler notation, by changing the sign of one specific axis of rotation, I got the correct registration. So, back to the quaternion notation used in the '.conf' file, after some tests I have noticed that by changing the sign of the 'w' component in the quaternion, in each 'bmesh' row, but the first (bun000.ply), the rotation by quaternion can be used.
Furthermore, for some reason, when registering the dragon (dragon_stand and dragon_side) and armadillo (armadillo_stand) stanford point clouds, in order to get the correct result I had to use a different sequence for reading the quaternion data in the ‘.conf’ file. It seems to be stored as:
tx ty tz qw qx qy qz
where 't' refers to a translation value and 'q' refers to a quaternion value. Just to be clear, I have just tested these three models, therefore, I don’t know what is the default pattern for the quaternion values. Besides, for these last two point cloud models, I did not need to change the '.conf' file.
I hope this could be useful for someone else trying to do the same
Just in case someone is looking for a full python implementation on the basis of what #DanceIgel found out, here is some code in python 3.9.1, also generating a figure in mathplotlib:
# Python 3.9.1
import numpy as np
import sys
import math
import glob
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import open3d as o3d
def get_pointcloud_files(path):
files = list()
for f in glob.glob(path + '/*.ply'):
files.append(f)
return files
def get_pointcloud_from_file(path, filename):
cloud = o3d.io.read_point_cloud(path + '/' + filename)
return cloud
def get_transformations_from_file(path, filename):
with open(path + '/' + filename) as f:
lines = (line for line in f)
source = np.loadtxt(lines, delimiter=' ', skiprows=1, dtype='str')
source = np.delete(source, 0, 1) #remove camera
filenames = source[:,0]
source = source[filenames.argsort()]
filenames = np.sort(filenames)
translations = list()
for row in source[:,1:4]:
translations.append(np.reshape(row, [3,1]).astype(np.float32))
quaternions = list()
for row in source[:,4:]:
quaternions.append(np.reshape(row, [4,1]).astype(np.float32))
return filenames, translations, quaternions
def quaternion_rotation_matrix(Q):
# Extract the values from Q
q0 = Q[3]
q1 = Q[0]
q2 = Q[1]
q3 = Q[2]
# calculate unit quarternion
magnitude = math.sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3)
q0 = q0 / magnitude
q1 = q1 / magnitude
q2 = q2 / magnitude
q3 = q3 / magnitude
# First row of the rotation matrix
r00 = 2 * (q0 * q0 + q1 * q1) - 1
r01 = 2 * (q1 * q2 - q0 * q3)
r02 = 2 * (q1 * q3 + q0 * q2)
# Second row of the rotation matrix
r10 = 2 * (q1 * q2 + q0 * q3)
r11 = 2 * (q0 * q0 + q2 * q2) - 1
r12 = 2 * (q2 * q3 - q0 * q1)
# Third row of the rotation matrix
r20 = 2 * (q1 * q3 - q0 * q2)
r21 = 2 * (q2 * q3 + q0 * q1)
r22 = 2 * (q0 * q0 + q3 * q3) - 1
# 3x3 rotation matrix
rot_matrix = np.array([[r00, r01, r02],
[r10, r11, r12],
[r20, r21, r22]])
rot_matrix = np.transpose(rot_matrix)
return rot_matrix
if __name__=="__main__": # $python visualization_bunny.py bunny/data
path = sys.argv[1]
# load transformations and filenames from file
filenames, translations, quaternions = get_transformations_from_file(path, 'bun.conf')
curr_transformation = np.zeros([3,4])
clouds = list()
for curr_filename, curr_quaternion, curr_translation in zip(filenames, quaternions, translations): # go through input files
curr_cloud = get_pointcloud_from_file(path, curr_filename)
# convert cloud to numpy
curr_cloud = np.asarray(curr_cloud.points)
# compute rotation matrix from quaternions
curr_rotation_matr = quaternion_rotation_matrix(curr_quaternion)
curr_rotation_matr = np.squeeze(curr_rotation_matr)
curr_translation = np.squeeze(curr_translation)
# create transformation matrix
curr_transformation[:,0:3] = curr_rotation_matr
curr_transformation[:,3] = curr_translation
# transform current cloud
for i in range(curr_cloud.shape[0]):
# apply rotation
curr_point = np.matmul(curr_rotation_matr, np.transpose(curr_cloud[i,:]))
# apply translation
curr_point = curr_point + curr_translation
curr_cloud[i,0] = curr_point[0]
curr_cloud[i,1] = curr_point[1]
curr_cloud[i,2] = curr_point[2]
# add current cloud to list of clouds
clouds.append(curr_cloud)
#plot separate point clouds in same graph
ax = plt.axes(projection='3d')
for cloud in clouds:
ax.plot(cloud[:,0], cloud[:,1], cloud[:,2], 'bo', markersize=0.005)
#ax.view_init(elev=90, azim=270)
ax.view_init(elev=100, azim=270)
plt.axis('off')
plt.savefig("ZZZ_Stanford_Bunny_PointCloud.png", bbox_inches='tight')
plt.show()

Mandelbrot optimization in openmp

Well i have to paralellisize the mandelbrot program in C. I think i have done it well and i cant get better times. My question if someone has an idea to improve the code, ive been thinking perhaps in nested parallel regions between the outer and insider for...
Also i have doubts if its more elegant or recommended to put all the pragmas in a single line or to write separate pragmas ( one for omp parallel and shared and private variables and a conditional, and another pragma with omp for and schedule dynamic).
Ive the doubt if constants can be used as private variables because i think is cleaner to have constants instead of defined variables.
Also i have written a conditional ( if numcpu >1) it has no sense to use parallel region and make a normal sequential execution.
Finally as i have read the dynamic chunk it depends on hardware and your system configuration... so i have left it as a constant, so it can be easily changed.
Also i adapt the number of threads to the number of processors available..
int main(int argc, char *argv[])
{
omp_set_dynamic(1);
int xactual, yactual;
//each iteration, it calculates: newz = oldz*oldz + p, where p is the current pixel, and oldz stars at the origin
double pr, pi; //real and imaginary part of the pixel p
double newRe, newIm, oldRe, oldIm; //real and imaginary parts of new and old z
double zoom = 1, moveX = -0.5, moveY = 0; //you can change these to zoom and change position
pixel_t *pixels = malloc(sizeof(pixel_t)*IMAGEHEIGHT*IMAGEWIDTH);
clock_t begin, end;
double time_spent;
begin=clock();
int numcpu;
numcpu = omp_get_num_procs();
//FILE * fp;
printf("El número de procesadores que utilizaremos es: %d", numcpu);
omp_set_num_threads(numcpu);
#pragma omp parallel shared(pixels, moveX, moveY, zoom) private(xactual, yactual, pr, pi, newRe, newIm) (if numcpu>1)
{
//int xactual=0;
// int yactual=0;
#pragma omp for schedule(dynamic, CHUNK)
//loop through every pixel
for(yactual = 0; yactual < IMAGEHEIGHT; yactual++)
for(xactual = 0; xactual < IMAGEWIDTH; xactual++)
{
//calculate the initial real and imaginary part of z, based on the pixel location and zoom and position values
pr = 1.5 * (xactual - IMAGEWIDTH / 2) / (0.5 * zoom * IMAGEWIDTH) + moveX;
pi = (yactual - IMAGEHEIGHT / 2) / (0.5 * zoom * IMAGEHEIGHT) + moveY;
newRe = newIm = oldRe = oldIm = 0; //these should start at 0,0
//"i" will represent the number of iterations
int i;
//start the iteration process
for(i = 0; i < ITERATIONS; i++)
{
//remember value of previous iteration
oldRe = newRe;
oldIm = newIm;
//the actual iteration, the real and imaginary part are calculated
newRe = oldRe * oldRe - oldIm * oldIm + pr;
newIm = 2 * oldRe * oldIm + pi;
//if the point is outside the circle with radius 2: stop
if((newRe * newRe + newIm * newIm) > 4) break;
}
// color(i % 256, 255, 255 * (i < maxIterations));
if(i == ITERATIONS)
{
//color(0, 0, 0); // black
pixels[yactual*IMAGEWIDTH+xactual][0] = 0;
pixels[yactual*IMAGEWIDTH+xactual][1] = 0;
pixels[yactual*IMAGEWIDTH+xactual][2] = 0;
}
else
{
double z = sqrt(newRe * newRe + newIm * newIm);
int brightness = 256 * log2(1.75 + i - log2(log2(z))) / log2((double)ITERATIONS);
//color(brightness, brightness, 255)
pixels[yactual*IMAGEWIDTH+xactual][0] = brightness;
pixels[yactual*IMAGEWIDTH+xactual][1] = brightness;
pixels[yactual*IMAGEWIDTH+xactual][2] = 255;
}
}
} //end of parallel region
end= clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
fprintf(stderr, "Elapsed time: %.2lf seconds.\n", time_spent);
You could extend the implementation to leverage SIMD extensions. As far as I know the latest OpenMP standard includes vector constructs. Check out this article that describes the new capabilities.
This whitepaper explains how SSE3 can be used when calculating the Mandelbrot set.

NSBitmapImageRep:colorAtX:y returned NSColor keeps growing on the heap

I'm comparing the "color distance" between two images with the same width and height to see how similar they are - the measure of similarity is just comparing them pixel by pixel and seeing how far each of their color channels are from one another.
- (NSNumber*) calculateFitness:(NSImage*)currentImage
andDestinationImage:(NSImage*)destinationImage {
NSData *tiffData = [currentImage TIFFRepresentation];
NSBitmapImageRep *currentImageRep = [NSBitmapImageRep
imageRepWithData:tiffData];
NSData *destinationImageTiffData = [destinationImage TIFFRepresentation];
NSBitmapImageRep *destinationImageRep = [NSBitmapImageRep imageRepWithData:destinationImageTiffData];
long fitnessScore = 0;
for (int width = 0; width < currentImageRep.size.width; width++) {
for (int height = 0; height < currentImageRep.size.height; height++) {
NSColor *destinationColor = [destinationImageRep colorAtX:width y:height];
NSColor *currentColor = [currentImageRep.size.height colorAtX:width y:height];
CGFloat deltaRed = (currentColor.redComponent - destinationColor.redComponent) * 255;
CGFloat deltaGreen = (currentColor.greenComponent - destinationColor.greenComponent) * 255;
CGFloat deltaBlue = (currentColor.blueComponent - destinationColor.blueComponent) * 255;
fitnessScore += (deltaRed * deltaRed) +
(deltaGreen * deltaGreen) +
(deltaBlue * deltaBlue);
}
}
}
I call this method many many times in my program to compare the fitness of thousands of images to one another. What I'm noticing in instruments is that the number of living NSCalibratedRGBColor objects keeps growing and it's due to the destinationColor and currentColor objects being created with NSBitmapImageRep:colorAtX:y above. Eventually, my entire system memory will be consumed.
So - is there a reason why this happens? What am I doing wrong? Is there a more efficient way to get the raw bitmap data for my images?
Thanks
Mustafa
You might get better performance by using the raw bitmap data. NSBitmapImageRep's -colorAtX:y: (and -getPixel:atX:y) are quite slow if you're going through all the image data. Also all the NSColors allocated will be held in the autorelease pool until your app returns to the main loop .
unsigned char *currentData = [currentImageRep bitmapData];
unsigned char *destinationData = [destinationImageRep bitmapData];
NSUInteger width = [currentImageRep pixelWidth];
NSUInteger height = [currentImageRep pixelHeight];
NSUInteger currentBytesPerRow = [currentImageRep bytesPerRow];
NSUInteger destBytesPerRow = [destinationImageRep bytesPerRow];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
unsigned char *srcPixel = currentData + ((x * 4) + (y * currentBytesPerRow));
unsigned char *destPixel = destinationData + ((x * 4) + (y * destBytesPerRow));
char sr, sg, sb;
char dr, dg, db;
sr = *srcPixel;
sg = *(srcPixel + 1);
sb = *(srcPixel + 2);
dr = *destPixel;
dg = *(destPixel + 1);
db = *(destPixel + 2);
CGFloat deltaRed = (sr - dr);
CGFloat deltaGreen = (sg - dg);
CGFloat deltaBlue = (sb - db);
fitnessScore += (deltaRed * deltaRed) +
(deltaGreen * deltaGreen) +
(deltaBlue * deltaBlue);
}
}
I wrote this https://medium.com/#iainx/fast-colour-analysis-d8b6422c1135 on doing fast colour analysis, and this was one of the things I discovered.

How to compare two colors for similarity/difference

I want to design a program that can help me assess between 5 pre-defined colors which one is more similar to a variable color, and with what percentage. The thing is that I don't know how to do that manually step by step. So it is even more difficult to think of a program.
More details: The colors are from photographs of tubes with gel that as different colors. I have 5 tubes with different colors were each is representative of 1 of 5 levels. I want to take photographs of other samples and on the computer assess to which level that sample belongs by comparing colors, and I want to know that with a percentage of approximation too. I would like a program that does something like this: http://www.colortools.net/color_matcher.html
If you can tell me what steps to take, even if they are things for me to think and do manually. It would be very helpful.
See Wikipedia's article on Color Difference for the right leads.
Basically, you want to compute a distance metric in some multidimensional colorspace.
But RGB is not "perceptually uniform", so your Euclidean RGB distance metric suggested by Vadim will not match the human-perceived distance between colors. For a start, L*a*b* is intended to be a perceptually uniform colorspace, and the deltaE metric is commonly used. But there are more refined colorspaces and more refined deltaE formulas that get closer to matching human perception.
You'll have to learn more about colorspaces and illuminants to do the conversions. But for a quick formula that is better than the Euclidean RGB metric, just do this:
Assume that your RGB values are in the sRGB colorspace
Find the sRGB to L*a*b* conversion formulas
Convert your sRGB colors to L*a*b*
Compute deltaE between your two L*a*b* values
It's not computationally expensive, it's just some nonlinear formulas and some multiplications and additions.
Just an idea that first came to my mind (sorry if stupid).
Three components of colors can be assumed 3D coordinates of points and then you could calculate distance between points.
F.E.
Point1 has R1 G1 B1
Point2 has R2 G2 B2
Distance between colors is
d=sqrt((r2-r1)^2+(g2-g1)^2+(b2-b1)^2)
Percentage is
p=d/sqrt((255)^2+(255)^2+(255)^2)
Actually I walked the same path a couple of months ago. There is no perfect answer to the question (that was asked here a couple of times) but there is one, more sophisticated than the sqrt(r-r) etc. answer and more easy to implement directly with RGB without moving to all kinds of alternate color spaces. I found this formula here which is a low cost approximation of the quite complicated real formula (by the CIE which is the W3C of colors, since this is a not finished quest, you can find older and simpler color difference equations there).
Good Luck.
Edit: For posterity, here's the relevant C code:
typedef struct {
unsigned char r, g, b;
} RGB;
double ColourDistance(RGB e1, RGB e2)
{
long rmean = ( (long)e1.r + (long)e2.r ) / 2;
long r = (long)e1.r - (long)e2.r;
long g = (long)e1.g - (long)e2.g;
long b = (long)e1.b - (long)e2.b;
return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}
If you have two Color objects c1 and c2, you can just compare each RGB value from c1 with that of c2.
int diffRed = Math.abs(c1.getRed() - c2.getRed());
int diffGreen = Math.abs(c1.getGreen() - c2.getGreen());
int diffBlue = Math.abs(c1.getBlue() - c2.getBlue());
Those values you can just divide by the amount of difference saturations (255), and you will get the difference between the two.
float pctDiffRed = (float)diffRed / 255;
float pctDiffGreen = (float)diffGreen / 255;
float pctDiffBlue = (float)diffBlue / 255;
After which you can just find the average color difference in percentage.
(pctDiffRed + pctDiffGreen + pctDiffBlue) / 3 * 100
Which would give you a difference in percentage between c1 and c2.
A color value has more than one dimension, so there is no intrinsic way to compare two colors. You have to determine for your use case the meaning of the colors and thereby how to best compare them.
Most likely you want to compare the hue, saturation and/or lightness properties of the colors as oppposed to the red/green/blue components. If you are having trouble figuring out how you want to compare them, take some pairs of sample colors and compare them mentally, then try to justify/explain to yourself why they are similar/different.
Once you know which properties/components of the colors you want to compare, then you need to figure out how to extract that information from a color.
Most likely you will just need to convert the color from the common RedGreenBlue representation to HueSaturationLightness, and then calculate something like
avghue = (color1.hue + color2.hue)/2
distance = abs(color1.hue-avghue)
This example would give you a simple scalar value indicating how far the gradient/hue of the colors are from each other.
See HSL and HSV at Wikipedia.
One of the best methods to compare two colors by human perception is CIE76. The difference is called Delta-E. When it is less than 1, the human eye can not recognize the difference.
There is wonderful color utilities class ColorUtils (code below), which includes CIE76 comparison methods. It is written by Daniel Strebel,University of Zurich.
From ColorUtils.class I use the method:
static double colorDifference(int r1, int g1, int b1, int r2, int g2, int b2)
r1,g1,b1 - RGB values of the first color
r2,g2,b2 - RGB values ot the second color that you would like to compare
If you work with Android, you can get these values like this:
r1 = Color.red(pixel);
g1 = Color.green(pixel);
b1 = Color.blue(pixel);
ColorUtils.class by Daniel Strebel,University of Zurich:
import android.graphics.Color;
public class ColorUtil {
public static int argb(int R, int G, int B) {
return argb(Byte.MAX_VALUE, R, G, B);
}
public static int argb(int A, int R, int G, int B) {
byte[] colorByteArr = {(byte) A, (byte) R, (byte) G, (byte) B};
return byteArrToInt(colorByteArr);
}
public static int[] rgb(int argb) {
return new int[]{(argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF};
}
public static int byteArrToInt(byte[] colorByteArr) {
return (colorByteArr[0] << 24) + ((colorByteArr[1] & 0xFF) << 16)
+ ((colorByteArr[2] & 0xFF) << 8) + (colorByteArr[3] & 0xFF);
}
public static int[] rgb2lab(int R, int G, int B) {
//http://www.brucelindbloom.com
float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
float Ls, as, bs;
float eps = 216.f / 24389.f;
float k = 24389.f / 27.f;
float Xr = 0.964221f; // reference white D50
float Yr = 1.0f;
float Zr = 0.825211f;
// RGB to XYZ
r = R / 255.f; //R 0..1
g = G / 255.f; //G 0..1
b = B / 255.f; //B 0..1
// assuming sRGB (D65)
if (r <= 0.04045)
r = r / 12;
else
r = (float) Math.pow((r + 0.055) / 1.055, 2.4);
if (g <= 0.04045)
g = g / 12;
else
g = (float) Math.pow((g + 0.055) / 1.055, 2.4);
if (b <= 0.04045)
b = b / 12;
else
b = (float) Math.pow((b + 0.055) / 1.055, 2.4);
X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;
// XYZ to Lab
xr = X / Xr;
yr = Y / Yr;
zr = Z / Zr;
if (xr > eps)
fx = (float) Math.pow(xr, 1 / 3.);
else
fx = (float) ((k * xr + 16.) / 116.);
if (yr > eps)
fy = (float) Math.pow(yr, 1 / 3.);
else
fy = (float) ((k * yr + 16.) / 116.);
if (zr > eps)
fz = (float) Math.pow(zr, 1 / 3.);
else
fz = (float) ((k * zr + 16.) / 116);
Ls = (116 * fy) - 16;
as = 500 * (fx - fy);
bs = 200 * (fy - fz);
int[] lab = new int[3];
lab[0] = (int) (2.55 * Ls + .5);
lab[1] = (int) (as + .5);
lab[2] = (int) (bs + .5);
return lab;
}
/**
* Computes the difference between two RGB colors by converting them to the L*a*b scale and
* comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}
*/
public static double getColorDifference(int a, int b) {
int r1, g1, b1, r2, g2, b2;
r1 = Color.red(a);
g1 = Color.green(a);
b1 = Color.blue(a);
r2 = Color.red(b);
g2 = Color.green(b);
b2 = Color.blue(b);
int[] lab1 = rgb2lab(r1, g1, b1);
int[] lab2 = rgb2lab(r2, g2, b2);
return Math.sqrt(Math.pow(lab2[0] - lab1[0], 2) + Math.pow(lab2[1] - lab1[1], 2) + Math.pow(lab2[2] - lab1[2], 2));
}
}
Just another answer, although it's similar to Supr's one - just a different color space.
The thing is: Humans perceive the difference in color not uniformly and the RGB color space is ignoring this. As a result if you use the RGB color space and just compute the euclidean distance between 2 colors you may get a difference which is mathematically absolutely correct, but wouldn't coincide with what humans would tell you.
This may not be a problem - the difference is not that large I think, but if you want to solve this "better" you should convert your RGB colors into a color space that was specifically designed to avoid the above problem. There are several ones, improvements from earlier models (since this is based on human perception we need to measure the "correct" values based on experimental data). There's the Lab colorspace which I think would be the best although a bit complicated to convert it to. Simpler would be the CIE XYZ one.
Here's a site that lists the formula's to convert between different color spaces so you can experiment a bit.
Kotlin version with how much percent do you want to match.
Method call with percent optional argument
isMatchingColor(intColor1, intColor2, 95) // should match color if 95% similar
Method body
private fun isMatchingColor(intColor1: Int, intColor2: Int, percent: Int = 90): Boolean {
val threadSold = 255 - (255 / 100f * percent)
val diffAlpha = abs(Color.alpha(intColor1) - Color.alpha(intColor2))
val diffRed = abs(Color.red(intColor1) - Color.red(intColor2))
val diffGreen = abs(Color.green(intColor1) - Color.green(intColor2))
val diffBlue = abs(Color.blue(intColor1) - Color.blue(intColor2))
if (diffAlpha > threadSold) {
return false
}
if (diffRed > threadSold) {
return false
}
if (diffGreen > threadSold) {
return false
}
if (diffBlue > threadSold) {
return false
}
return true
}
All methods below result in a scale from 0-100.
internal static class ColorDifference
{
internal enum Method
{
Binary, // true or false, 0 is false
Square,
Dimensional,
CIE76
}
public static double Calculate(Method method, int argb1, int argb2)
{
int[] c1 = ColorConversion.ArgbToArray(argb1);
int[] c2 = ColorConversion.ArgbToArray(argb2);
return Calculate(method, c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
}
public static double Calculate(Method method, int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1)
{
switch (method)
{
case Method.Binary:
return (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2) ? 0 : 100;
case Method.CIE76:
return CalculateCIE76(r1, r2, g1, g2, b1, b2);
case Method.Dimensional:
if (a1 == -1 || a2 == -1) return Calculate3D(r1, r2, g1, g2, b1, b2);
else return Calculate4D(r1, r2, g1, g2, b1, b2, a1, a2);
case Method.Square:
return CalculateSquare(r1, r2, g1, g2, b1, b2, a1, a2);
default:
throw new InvalidOperationException();
}
}
public static double Calculate(Method method, Color c1, Color c2, bool alpha)
{
switch (method)
{
case Method.Binary:
return (c1.R == c2.R && c1.G == c2.G && c1.B == c2.B && (!alpha || c1.A == c2.A)) ? 0 : 100;
case Method.CIE76:
if (alpha) throw new InvalidOperationException();
return CalculateCIE76(c1, c2);
case Method.Dimensional:
if (alpha) return Calculate4D(c1, c2);
else return Calculate3D(c1, c2);
case Method.Square:
if (alpha) return CalculateSquareAlpha(c1, c2);
else return CalculateSquare(c1, c2);
default:
throw new InvalidOperationException();
}
}
// A simple idea, based on on a Square
public static double CalculateSquare(int argb1, int argb2)
{
int[] c1 = ColorConversion.ArgbToArray(argb1);
int[] c2 = ColorConversion.ArgbToArray(argb2);
return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]);
}
public static double CalculateSquare(Color c1, Color c2)
{
return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
}
public static double CalculateSquareAlpha(int argb1, int argb2)
{
int[] c1 = ColorConversion.ArgbToArray(argb1);
int[] c2 = ColorConversion.ArgbToArray(argb2);
return CalculateSquare(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
}
public static double CalculateSquareAlpha(Color c1, Color c2)
{
return CalculateSquare(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A);
}
public static double CalculateSquare(int r1, int r2, int g1, int g2, int b1, int b2, int a1 = -1, int a2 = -1)
{
if (a1 == -1 || a2 == -1) return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2)) / 7.65;
else return (Math.Abs(r1 - r2) + Math.Abs(g1 - g2) + Math.Abs(b1 - b2) + Math.Abs(a1 - a2)) / 10.2;
}
// from:http://stackoverflow.com/questions/9018016/how-to-compare-two-colors
public static double Calculate3D(int argb1, int argb2)
{
int[] c1 = ColorConversion.ArgbToArray(argb1);
int[] c2 = ColorConversion.ArgbToArray(argb2);
return Calculate3D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3]);
}
public static double Calculate3D(Color c1, Color c2)
{
return Calculate3D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
}
public static double Calculate3D(int r1, int r2, int g1, int g2, int b1, int b2)
{
return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2)) / 4.41672955930063709849498817084;
}
// Same as above, but made 4D to include alpha channel
public static double Calculate4D(int argb1, int argb2)
{
int[] c1 = ColorConversion.ArgbToArray(argb1);
int[] c2 = ColorConversion.ArgbToArray(argb2);
return Calculate4D(c1[1], c2[1], c1[2], c2[2], c1[3], c2[3], c1[0], c2[0]);
}
public static double Calculate4D(Color c1, Color c2)
{
return Calculate4D(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B, c1.A, c2.A);
}
public static double Calculate4D(int r1, int r2, int g1, int g2, int b1, int b2, int a1, int a2)
{
return Math.Sqrt(Math.Pow(Math.Abs(r1 - r2), 2) + Math.Pow(Math.Abs(g1 - g2), 2) + Math.Pow(Math.Abs(b1 - b2), 2) + Math.Pow(Math.Abs(a1 - a2), 2)) / 5.1;
}
/**
* Computes the difference between two RGB colors by converting them to the L*a*b scale and
* comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}
*/
public static double CalculateCIE76(int argb1, int argb2)
{
return CalculateCIE76(Color.FromArgb(argb1), Color.FromArgb(argb2));
}
public static double CalculateCIE76(Color c1, Color c2)
{
return CalculateCIE76(c1.R, c2.R, c1.G, c2.G, c1.B, c2.B);
}
public static double CalculateCIE76(int r1, int r2, int g1, int g2, int b1, int b2)
{
int[] lab1 = ColorConversion.ColorToLab(r1, g1, b1);
int[] lab2 = ColorConversion.ColorToLab(r2, g2, b2);
return Math.Sqrt(Math.Pow(lab2[0] - lab1[0], 2) + Math.Pow(lab2[1] - lab1[1], 2) + Math.Pow(lab2[2] - lab1[2], 2)) / 2.55;
}
}
internal static class ColorConversion
{
public static int[] ArgbToArray(int argb)
{
return new int[] { (argb >> 24), (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF };
}
public static int[] ColorToLab(int R, int G, int B)
{
// http://www.brucelindbloom.com
double r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
double Ls, fas, fbs;
double eps = 216.0f / 24389.0f;
double k = 24389.0f / 27.0f;
double Xr = 0.964221f; // reference white D50
double Yr = 1.0f;
double Zr = 0.825211f;
// RGB to XYZ
r = R / 255.0f; //R 0..1
g = G / 255.0f; //G 0..1
b = B / 255.0f; //B 0..1
// assuming sRGB (D65)
if (r <= 0.04045) r = r / 12;
else r = (float)Math.Pow((r + 0.055) / 1.055, 2.4);
if (g <= 0.04045) g = g / 12;
else g = (float)Math.Pow((g + 0.055) / 1.055, 2.4);
if (b <= 0.04045) b = b / 12;
else b = (float)Math.Pow((b + 0.055) / 1.055, 2.4);
X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;
// XYZ to Lab
xr = X / Xr;
yr = Y / Yr;
zr = Z / Zr;
if (xr > eps) fx = (float)Math.Pow(xr, 1 / 3.0);
else fx = (float)((k * xr + 16.0) / 116.0);
if (yr > eps) fy = (float)Math.Pow(yr, 1 / 3.0);
else fy = (float)((k * yr + 16.0) / 116.0);
if (zr > eps) fz = (float)Math.Pow(zr, 1 / 3.0);
else fz = (float)((k * zr + 16.0) / 116);
Ls = (116 * fy) - 16;
fas = 500 * (fx - fy);
fbs = 200 * (fy - fz);
int[] lab = new int[3];
lab[0] = (int)(2.55 * Ls + 0.5);
lab[1] = (int)(fas + 0.5);
lab[2] = (int)(fbs + 0.5);
return lab;
}
}
A simple method that only uses RGB is
cR=R1-R2
cG=G1-G2
cB=B1-B2
uR=R1+R2
distance=cR*cR*(2+uR/256) + cG*cG*4 + cB*cB*(2+(255-uR)/256)
I've used this one for a while now, and it works well enough for most purposes.
I've tried various methods like LAB color space, HSV comparisons and I've found that luminosity works pretty well for this purpose.
Here is Python version
def lum(c):
def factor(component):
component = component / 255;
if (component <= 0.03928):
component = component / 12.92;
else:
component = math.pow(((component + 0.055) / 1.055), 2.4);
return component
components = [factor(ci) for ci in c]
return (components[0] * 0.2126 + components[1] * 0.7152 + components[2] * 0.0722) + 0.05;
def color_distance(c1, c2):
l1 = lum(c1)
l2 = lum(c2)
higher = max(l1, l2)
lower = min(l1, l2)
return (higher - lower) / higher
c1 = ImageColor.getrgb('white')
c2 = ImageColor.getrgb('yellow')
print(color_distance(c1, c2))
Will give you
0.0687619047619048
Android for ColorUtils API RGBToHSL:
I had two int argb colors (color1, color2) and I wanted to get distance/difference among the two colors. Here is what I did;
private float getHue(int color) {
int R = (color >> 16) & 0xff;
int G = (color >> 8) & 0xff;
int B = (color ) & 0xff;
float[] colorHue = new float[3];
ColorUtils.RGBToHSL(R, G, B, colorHue);
return colorHue[0];
}
Then I used below code to find the distance between the two colors.
private float getDistance(getHue(color1), getHue(color2)) {
float avgHue = (hue1 + hue2)/2;
return Math.abs(hue1 - avgHue);
}
The best way is deltaE. DeltaE is a number that shows the difference of the colors. If deltae < 1 then the difference can't recognize by human eyes. I wrote a code in canvas and js for converting rgb to lab and then calculating delta e. On this example the code is recognising pixels which have different color with a base color that I saved as LAB1. and then if it is different makes those pixels red. You can increase or reduce the sensitivity of the color difference with increae or decrease the acceptable range of delta e. In this example I assigned 10 for deltaE in the line that I wrote (deltae <= 10):
<script>
var constants = {
canvasWidth: 700, // In pixels.
canvasHeight: 600, // In pixels.
colorMap: new Array()
};
// -----------------------------------------------------------------------------------------------------
function fillcolormap(imageObj1) {
function rgbtoxyz(red1,green1,blue1){ // a converter for converting rgb model to xyz model
var red2 = red1/255;
var green2 = green1/255;
var blue2 = blue1/255;
if(red2>0.04045){
red2 = (red2+0.055)/1.055;
red2 = Math.pow(red2,2.4);
}
else{
red2 = red2/12.92;
}
if(green2>0.04045){
green2 = (green2+0.055)/1.055;
green2 = Math.pow(green2,2.4);
}
else{
green2 = green2/12.92;
}
if(blue2>0.04045){
blue2 = (blue2+0.055)/1.055;
blue2 = Math.pow(blue2,2.4);
}
else{
blue2 = blue2/12.92;
}
red2 = (red2*100);
green2 = (green2*100);
blue2 = (blue2*100);
var x = (red2 * 0.4124) + (green2 * 0.3576) + (blue2 * 0.1805);
var y = (red2 * 0.2126) + (green2 * 0.7152) + (blue2 * 0.0722);
var z = (red2 * 0.0193) + (green2 * 0.1192) + (blue2 * 0.9505);
var xyzresult = new Array();
xyzresult[0] = x;
xyzresult[1] = y;
xyzresult[2] = z;
return(xyzresult);
} //end of rgb_to_xyz function
function xyztolab(xyz){ //a convertor from xyz to lab model
var x = xyz[0];
var y = xyz[1];
var z = xyz[2];
var x2 = x/95.047;
var y2 = y/100;
var z2 = z/108.883;
if(x2>0.008856){
x2 = Math.pow(x2,1/3);
}
else{
x2 = (7.787*x2) + (16/116);
}
if(y2>0.008856){
y2 = Math.pow(y2,1/3);
}
else{
y2 = (7.787*y2) + (16/116);
}
if(z2>0.008856){
z2 = Math.pow(z2,1/3);
}
else{
z2 = (7.787*z2) + (16/116);
}
var l= 116*y2 - 16;
var a= 500*(x2-y2);
var b= 200*(y2-z2);
var labresult = new Array();
labresult[0] = l;
labresult[1] = a;
labresult[2] = b;
return(labresult);
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var imageX = 0;
var imageY = 0;
context.drawImage(imageObj1, imageX, imageY, 240, 140);
var imageData = context.getImageData(0, 0, 240, 140);
var data = imageData.data;
var n = data.length;
// iterate over all pixels
var m = 0;
for (var i = 0; i < n; i += 4) {
var red = data[i];
var green = data[i + 1];
var blue = data[i + 2];
var xyzcolor = new Array();
xyzcolor = rgbtoxyz(red,green,blue);
var lab = new Array();
lab = xyztolab(xyzcolor);
constants.colorMap.push(lab); //fill up the colormap array with lab colors.
}
}
// -----------------------------------------------------------------------------------------------------
function colorize(pixqty) {
function deltae94(lab1,lab2){ //calculating Delta E 1994
var c1 = Math.sqrt((lab1[1]*lab1[1])+(lab1[2]*lab1[2]));
var c2 = Math.sqrt((lab2[1]*lab2[1])+(lab2[2]*lab2[2]));
var dc = c1-c2;
var dl = lab1[0]-lab2[0];
var da = lab1[1]-lab2[1];
var db = lab1[2]-lab2[2];
var dh = Math.sqrt((da*da)+(db*db)-(dc*dc));
var first = dl;
var second = dc/(1+(0.045*c1));
var third = dh/(1+(0.015*c1));
var deresult = Math.sqrt((first*first)+(second*second)+(third*third));
return(deresult);
} // end of deltae94 function
var lab11 = new Array("80","-4","21");
var lab12 = new Array();
var k2=0;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var imageData = context.getImageData(0, 0, 240, 140);
var data = imageData.data;
for (var i=0; i<pixqty; i++) {
lab12 = constants.colorMap[i];
var deltae = deltae94(lab11,lab12);
if (deltae <= 10) {
data[i*4] = 255;
data[(i*4)+1] = 0;
data[(i*4)+2] = 0;
k2++;
} // end of if
} //end of for loop
context.clearRect(0,0,240,140);
alert(k2);
context.putImageData(imageData,0,0);
}
// -----------------------------------------------------------------------------------------------------
$(window).load(function () {
var imageObj = new Image();
imageObj.onload = function() {
fillcolormap(imageObj);
}
imageObj.src = './mixcolor.png';
});
// ---------------------------------------------------------------------------------------------------
var pixno2 = 240*140;
</script>
I used this in my android up and it seems satisfactory although RGB space is not recommended:
public double colourDistance(int red1,int green1, int blue1, int red2, int green2, int blue2)
{
double rmean = ( red1 + red2 )/2;
int r = red1 - red2;
int g = green1 - green2;
int b = blue1 - blue2;
double weightR = 2 + rmean/256;
double weightG = 4.0;
double weightB = 2 + (255-rmean)/256;
return Math.sqrt(weightR*r*r + weightG*g*g + weightB*b*b);
}
Then I used the following to get percent of similarity:
double maxColDist = 764.8339663572415;
double d1 = colourDistance(red1,green1,blue1,red2,green2,blue2);
String s1 = (int) Math.round(((maxColDist-d1)/maxColDist)*100) + "% match";
It works well enough.
I expect you want to analyze a whole image at the end, don't you? So you could check for the smallest/highest difference to the identity color matrix.
Most math operations for processing graphics use matrices, because the possible algorithms using them are often faster than classical point by point distance and comparism calculations. (e.g. for operations using DirectX, OpenGL, ...)
So I think you should start here:
http://en.wikipedia.org/wiki/Identity_matrix
http://en.wikipedia.org/wiki/Matrix_difference_equation
... and as Beska already commented above:
This may not give the best "visible" difference...
Which means also that your algorithm depends onto your definiton of "similar to" if you are processing images.
You'll need to convert any RGB colors into the Lab color space to be able to compare them in the way that humans see them. Otherwise you'll be getting RGB colors that 'match' in some very strange ways.
The wikipedia link on Color Differences gives you an intro into the various Lab color space difference algorithms that have been defined over the years. The simplest that just checks the Euclidian distance of two lab colours, works but has a few flaws.
Conveniently there's a Java implementation of the more sophisticated CIEDE2000 algorithm in the OpenIMAJ project. Provide it your two sets of Lab colours and it'll give you back single distance value.
The only "right" way to compare colors is to do it with deltaE in CIELab or CIELuv.
But for a lot of applications I think this is a good enough approximation:
distance = 3 * |dR| + 4 * |dG| + 3 * |dB|
I think a weighted manhattan distance makes a lot more sense when comparing colors. Remember that color primaries are only in our head. They don't have any physical significance. CIELab and CIELuv is modelled statistically from our perception of color.
For quick and dirty, you can do
import java.awt.Color;
private Color dropPrecision(Color c,int threshold){
return new Color((c.getRed()/threshold),
(c.getGreen()/threshold),
(c.getBlue()/threshold));
}
public boolean inThreshold(Color _1,Color _2,int threshold){
return dropPrecision(_1,threshold)==dropPrecision(_2,threshold);
}
making use of integer division to quantize the colors.
Swift 5 Answer
I found this thread because I needed a Swift version of this question. As nobody has answered with the solution, here's mine:
extension UIColor {
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return (red, green, blue, alpha)
}
func isSimilar(to colorB: UIColor) -> Bool {
let rgbA = self.rgba
let rgbB = colorB.rgba
let diffRed = abs(CGFloat(rgbA.red) - CGFloat(rgbB.red))
let diffGreen = abs(rgbA.green - rgbB.green)
let diffBlue = abs(rgbA.blue - rgbB.blue)
let pctRed = diffRed
let pctGreen = diffGreen
let pctBlue = diffBlue
let pct = (pctRed + pctGreen + pctBlue) / 3 * 100
return pct < 10 ? true : false
}
}
Usage:
let black: UIColor = UIColor.black
let white: UIColor = UIColor.white
let similar: Bool = black.isSimilar(to: white)
I set less than 10% difference to return similar colours, but you can customise this yourself.

Resources