Newer
Older
AppendIrisCodeUtils / casic / iris / CasicIrisRec.cpp
#include "CasicIrisRec.h"
#include <math.h>
#include <fstream>

#include <Eigen/Dense>
#include <Eigen/Core>
#include <opencv2/opencv.hpp>
#include<opencv2/core/eigen.hpp>

using Eigen::MatrixXd;
using Eigen::MatrixXi;

namespace iristrt
{
CasicIrisRec::CasicIrisRec()
{

}

CasicIrisRec::CasicIrisRec(const char * gaborFilterFileName,const char * applicationPointsFileName)
{
    loadApplicationPoints(applicationPointsFileName);
    loadGaborFilters(gaborFilterFileName);
}

CasicIrisRec::~CasicIrisRec()
{

}

cv::Mat CasicIrisRec::normalize(cv::Mat image, int iris_x, int iris_y, int iris_r,int pupil_x, int pupil_y, int pupil_r)
{
    int width = NORM_WIDTH;
    int height = NORM_HEIGHT;
    int realHeight = height + 2;
    int angledivisions = width - 1;
    int rows = image.rows;
    int cols = image.cols;

    double ox = pupil_x - iris_x;
    double oy = pupil_y - iris_y;

    int sgn = ox <= 0 ? -1 : 1;
    if (ox == 0 && oy > 0) {
        sgn = 1;
    }

    double phi = ox == 0 ? M_PI / 2 : atan(oy / ox);

    MatrixXd a = MatrixXd::Ones(1, width) * (ox * ox + oy * oy);

    MatrixXd thetas(1, width);
    for (int i = 0; i < width; i++) {
        thetas(0, i) = (2 * M_PI / angledivisions) * i;
    }

    MatrixXd b = M_PI - phi - thetas.array();
    b = b.array().cos();
    b = sgn * b;

    MatrixXd item1 = a.array().sqrt() * b.array();
    MatrixXd item2 = a.array() * b.array().pow(2);
    MatrixXd item3 = a.array() - (iris_r * iris_r);
    MatrixXd r = item1 + (item2 - item3).array().sqrt().matrix();
    r = r.array() -  pupil_r;

    MatrixXd rMatrix = MatrixXd::Ones(1, realHeight).transpose() * r;
    MatrixXd tmp(1, realHeight);
    for (int i = 0; i < realHeight; i++) {
        tmp(0, i) = (double)(1.0 / realHeight) * i;
    }
    rMatrix = rMatrix.array() * (MatrixXd::Ones(angledivisions + 1, 1) * tmp).array().transpose();
    rMatrix = rMatrix.array() +  pupil_r;

    MatrixXd new_rMatrix = rMatrix.middleRows(1,realHeight-2);

    MatrixXd xcosMat = MatrixXd::Ones(height, 1) * thetas.array().cos().matrix();
    MatrixXd xsinMat = MatrixXd::Ones(height, 1) * thetas.array().sin().matrix();

    MatrixXd xo = pupil_x + new_rMatrix.array() * xcosMat.array();
    MatrixXd yo = pupil_y - new_rMatrix.array() * xsinMat.array();

    MatrixXi xo_i = xo.cast<int>();
    MatrixXi yo_i = yo.cast<int>();

    if(image.channels() == 3){
        cv::cvtColor(image,image,cv::COLOR_RGB2GRAY);
    }

    MatrixXi eigenImage;

    cv::cv2eigen(image, eigenImage);

    MatrixXi normMat = MatrixXi::Zero(xo_i.rows(), xo_i.cols());
    for (int i = 0; i < xo_i.rows(); i++) {
        for (int j = 0; j < xo_i.cols(); j++) {
            normMat(i, j) = eigenImage(yo_i(i, j), xo_i(i,j));
        }
    }

    cv::Mat normImage(image.size(),image.type());
    cv::eigen2cv(normMat, normImage);
    normImage.convertTo(normImage, CV_8UC1);

    return normImage;
}

std::vector<cv::Mat> CasicIrisRec::loadGaborFilters(const char * gaborFilterFileName)
{
    std::ifstream file(gaborFilterFileName, std::ios::in);
    if (!file)
    {
        std::cout << "Cannot load Gabor filters in file " << gaborFilterFileName << std::endl;
    }

    // Get the number of filters
    int n_filters;
    file >> n_filters;
    gaborFilters.resize(n_filters);

    //cout << "n_filters " << n_filters << endl;

    // Size of filter
    int rows, cols;

    // Loop on each filter
    for (int f = 0; f < n_filters; f++)
    {
        // Get the size of the filter
        file >> rows;
        file >> cols;
        //cout << rows << " " << cols << endl;
        // Temporary filter. Will be destroyed at the end of loop
        gaborFilters[f] = cv::Mat(rows, cols, CV_32FC1);

        // Set the value at coordinates r,c
        for (int r = 0; r < rows; r++)
        {
            for (int c = 0; c < cols; c++)
            {
                file >> gaborFilters[f].at<float>(r,c);
            }
        }

    } // Loop on each filter

    // Close the file
    file.close();

    return gaborFilters;
}

cv::Mat CasicIrisRec::loadApplicationPoints(const char * applicationPointsFileName)
{
    // Open text file containing the filters
    std::ifstream file(applicationPointsFileName, std::ios::in);
    if (!file)
    {
        std::cout << "Cannot load the application points in " << applicationPointsFileName << std::endl;
    }

    // Get the number of points
    int n_points = 0;
    file >> n_points;

    // Allocate memory for the matrix containing the points
    applicationPoints = cv::Mat(NORM_HEIGHT, NORM_WIDTH, CV_8UC1);
    // Initialize all pixels to "off"
    applicationPoints.setTo(cv::Scalar(0));

    // Local variables
    int i, j;

    // Loop on each point
    for (int p = 0; p < n_points; p++)
    {
        // Get the coordinates
        file >> i; file >> j;

        mvApplicationPoints.push_back(QPair<int, int>(i, j));

        // Set pixel to "on"
        if (i < 0 || i > applicationPoints.rows - 1 || j < 0 || j > applicationPoints.cols- 1)
        {
            std::cout << "Point (" << i << "," << j << ") ";
            std::cout << "exceeds size of normalized image : ";
            std::cout << applicationPoints.rows << "x" << applicationPoints.cols;
            std::cout << " while loading application points" << std::endl;
        }
        else
        {
            applicationPoints.at<uchar>(i,j) = 255;
        }
    }

    //cv::imshow("mpApplicationPoints", mpApplicationPoints);
    //cv::waitKey(0);

    // Close the file
    file.close();

    return applicationPoints;
}

cv::Mat CasicIrisRec::encodeToImage(cv::Mat normalizedImage)
{
    cv::Size size(normalizedImage.size());
    cv::Mat encodeImage(cv::Size(size.width, size.height * gaborFilters.size()), CV_32FC1);

    int max_width = 0;
    for (int f = 0; f < gaborFilters.size(); f++)
        if (gaborFilters[f].cols > max_width)
            max_width = gaborFilters[f].cols;
    max_width = (max_width - 1) / 2;

    cv::Mat resized = addBorders(normalizedImage, max_width);

//    std::cout << resized.size() << std::endl;
//    cv::imshow("resized", resized);
//    cv::waitKey(0);

    cv::Mat img1(resized.size(), CV_32F, 1);
    cv::Mat img2(resized.size(), encodeImage.depth(), 1);

    for (int f = 0; f < gaborFilters.size(); f++)
    {
        // Convolution
        cv::filter2D(resized, img1, img1.depth(), gaborFilters[f]);

        // Threshold : above or below 0
        cv::threshold(img1, img2, 0, 255, cv::THRESH_BINARY);

//        cv::imshow("img2", img2);
//        cv::waitKey(0);

        // Form the iris code
        cv::Mat roi = img2(cv::Rect(max_width, 0, normalizedImage.cols, normalizedImage.rows));
        cv::Mat dst = encodeImage(cv::Rect(0, f*normalizedImage.rows, normalizedImage.cols, normalizedImage.rows));
        roi.copyTo(dst);
        //cv::copyTo(img2, dst, NULL);
    }
    cv::Mat image8UC1;
    encodeImage.convertTo(image8UC1, CV_8UC1);
    return image8UC1;
}

QByteArray CasicIrisRec::extractFeature(cv::Mat irisCode, cv::Mat maskNorm)
{
    // count points width and height
    cv::Mat pointTemp(applicationPoints);
    cv::Mat rowTemp;

    std::vector<int> pointRows;
    for (int i = 0; i < applicationPoints.rows; i++) {
        rowTemp = pointTemp(cv::Rect(0, i, applicationPoints.cols, 1));
        if (cv::sum(rowTemp)[0] > 0) {
            pointRows.push_back(i);
        }
    }

    int width = irisCode.cols;
    int height = pointRows.size();
    rowTemp.release();
    pointTemp.release();

    int n_codes = irisCode.rows / applicationPoints.rows;

    int irisDataLength = (n_codes + 1) * width * height;
    uchar * irisData = new uchar[irisDataLength];
    int irisDataIndex = 0;

    uchar* codePixel = new uchar;

    // save iris code 512 * 8 * 6
    for (int n = 0; n < n_codes; n++) { // 6
        for (auto i : pointRows) { // 8
            i = n * maskNorm.rows + i;
            for (int j = 0; j < irisCode.cols; j++) { // 512
                codePixel = (uchar*)(irisCode.data + i * irisCode.step + j);
                if (*codePixel == 255) {
                    irisData[irisDataIndex++] = 1;
                }
                else {
                    irisData[irisDataIndex++] = *codePixel;
                }
            }
        }
    }

    // save iris mask 512 * 8 * 1
    uchar * maskPixel;
    for (auto i : pointRows) { // 8
        for (int j = 0; j < irisCode.cols; j++) { // 512
            maskPixel = (uchar*)(maskNorm.data + i * maskNorm.step + j);
            if (*maskPixel == 255) {
                irisData[irisDataIndex++] = 1;
            }
            else {
                irisData[irisDataIndex++] = *maskPixel;
            }
        }
    }


    uchar * irisFeaturePtr = new uchar[irisDataLength / 8 + 5];
    memset(irisFeaturePtr, 0, irisDataLength / 8 + 5);

    // first byte is width;
    irisFeaturePtr[0] = (uchar)width;
    irisFeaturePtr[1] = (uchar)(width >> 8);

    // second byte is height;
    irisFeaturePtr[2] = (uchar)height;

    // third byte is n_codes
    irisFeaturePtr[3] = (uchar)n_codes;

    // fourth byte is n_mask(1)
    irisFeaturePtr[4] = (uchar)1;
    for (int i = 0; i < irisDataLength; i++) {
        irisFeaturePtr[5 + i / 8] += irisData[i] << (7 - (i % 8));
    }

    QByteArray feature;
    for (int i = 0; i < irisDataLength / 8 + 5; i++) {
        feature.append((char) irisFeaturePtr[i]);
    }

    delete[] irisData;
    delete[] irisFeaturePtr;
    return feature;
}

float CasicIrisRec::matchImage(cv::Mat code1, cv::Mat code2, cv::Mat normalizedMask1, cv::Mat normalizedMask2)
{
    code1.convertTo(code1, CV_8UC1);
    code2.convertTo(code2, CV_8UC1);

    cv::Mat temp(applicationPoints.size(), CV_8UC1);
    temp.setTo(cv::Scalar(0));

    cv::bitwise_and(normalizedMask1, normalizedMask2, temp, applicationPoints);

    // Copy the mask f times, where f correspond to the number of codes (= number of filters)
    int n_codes = code1.rows / applicationPoints.rows;

    cv::Mat totalMask(code1.size(), CV_8UC1);
    for (int n = 0; n < n_codes; n++) {
        cv::Mat maskRoi = totalMask(cv::Rect(0, n*applicationPoints.rows, applicationPoints.cols, applicationPoints.rows));
        temp.copyTo(maskRoi);
    }

//    cv::imshow("totalMask", totalMask);
//    cv::waitKey(0);

    cv::Mat result(code1.size(), CV_8UC1);
    result.setTo(cv::Scalar(0));

    int shift = 10;
    cv::Mat shifted = addBorders(code1, shift);

    float score = 1;
    for (int s = -shift; s <= shift; s++) {
        cv::Mat roi = shifted(cv::Rect(shift + s, 0, code1.cols, code1.rows));
        cv::bitwise_xor(roi, code2, result, totalMask);

        float mean = (cv::sum(result).val[0]) / (cv::sum(totalMask).val[0]);
        // std::cout << "mean: " << mean << std::endl;
        score = std::min(score, mean);
    }

    // std::cout << "score: " << score << std::endl;
    return score;

}

float CasicIrisRec::matchFeatureCode(uchar *feature1, uchar *feature2)
{
    int width = feature1[0] | feature1[1] << 8; // 32
    int height = feature1[2]; // 8
    int nCode = feature1[3]; // 6
//    int nMask = feature1[4];

    uchar ** irisData1 = reduceIrisCode(feature1);
    uchar ** irisMask1 = reduceIrisMask(feature1);
    uchar ** irisData2 = reduceIrisCode(feature2);
    uchar ** irisMask2 = reduceIrisMask(feature2);

    int shift = 10;
    float score = 1.0;

    int minstep = 0 - shift;
    int maxstep = shift;

    for (int s = minstep; s <= maxstep; s++) {
        int count = 0;
        float mean = 0.0;
        for (int n = 0; n < nCode; n++) {
            for (auto pos : mvApplicationPoints) {
                int row = pos.first / mvApplicationPoints[0].first - 1;
                int col = (pos.second + s + width) % width;

                if (irisMask1[row][pos.second] == 1 && irisMask2[row][pos.second] == 1) {

                    uchar tmp1 = irisData1[row + height * n][col];
                    uchar tmp2 = irisData2[row + height * n][pos.second];
                    mean += tmp1 ^ tmp2;
                    count += 1;
                }
            }
        }

        //cout << "shift " << s << "  " << mean << "/" << count << " : " << mean / count << endl;
        mean = mean / count;
        score = mean < score ? mean : score;

    }

//    std::cout << "feature score: " << score << std::endl;


//    for (int i = 0; i < height * nCode; i++) {
//        delete irisData1[i];
//        delete irisData2[i];
//    }
//    delete irisData1;
//    delete irisData2;

//    for (int i = 0; i < height; i++) {
//        delete irisMask1[i];
//        delete irisMask2[i];
//    }
//    delete irisMask1;
//    delete irisMask2;
    return score;
}

cv::Mat CasicIrisRec::addBorders(cv::Mat pSrc, int width)
{
    cv::Mat result(cv::Size(pSrc.cols + 2 * width, pSrc.rows), pSrc.depth(), pSrc.channels());

    copyMakeBorder(pSrc, result, 0, 0, width, width, cv::BORDER_REPLICATE, 0);

    for (int i = 0; i < pSrc.rows; i++) {
        for (int j = 0; j < width; j++) {
            result.at<uchar>(i, j) = pSrc.at<uchar>(i, pSrc.cols - width + j);
            result.at<uchar>(i, result.cols - width + j) = pSrc.at<uchar>(i, j);
        }
    }
    return result;
}

uchar ** CasicIrisRec::reduceIrisCode(uchar *feature)
{
    int width = feature[0] | feature[1] << 8;
    int height = feature[2] * feature[3];

    uchar ** reduceCode = new uchar*[width*height];
    for (int i = 0; i < height; i++) {
        reduceCode[i] = new uchar[width];
        for (int j = 0; j < width; j++) {
            int tmp = i * width + j;
            reduceCode[i][j] = (uchar)feature[5 + tmp / 8] >> (7 - tmp % 8) & 1;
        }
    }

    return reduceCode;
}

uchar ** CasicIrisRec::reduceIrisMask(uchar *feature)
{
    int width = feature[0] | feature[1] << 8;
    int height = feature[2]; // 8
    int nCode = feature[3]; // 6

    uchar ** irisMask = new uchar*[width*height];
    for (int i = 0; i < height; i++) {
        irisMask[i] = new uchar[width];
        for (int j = 0; j < width; j++) {
            int tmp = width * height * nCode + i * width + j;
            irisMask[i][j] = (uchar)feature[5 + tmp / 8] >> (7 - tmp % 8) & 1;
        }
    }

    return irisMask;
}

}