用C++ CImage实现九宫格图片拼接

现将一张图片分成n*n张图片,每张图片宽度与高度一致,要求用CImage将随机排序的图片拼回原状

TIM截图20180720003357.jpg

大作业第4组测试案例3x3.jpg

思路分析

  1. 先用windows API读取文件夹下的图片列表
  2. 用CImage读取每张图片
  3. 读取每张图片的边界点并计算灰度值,公式: b 0.114 + g 0.587 + r * 0.299;
  4. 计算边界与对应边界之间的每个点的灰度值的平方差之和作为权值
  5. 找出权值最小且相同的对应边,构造n*n数组
  6. 绘制图片

难点

讲道理我也是刚刚才接触这些内容的,上手之后第一个问题就是string怎么转换为LPCTSTR使得CImage可以构造,具体解决是通过cstring作为中间转换

String str = "abc";
CString resultDir = str.c_str();
LPCTSTR destFilePath = (LPCTSTR)resultDir;

然后是权值的计算,一开始的解决方案是分别计算bgr的平方差,再求和,这样就导致了相邻的边却不是最小值这种情况(可跑出案例中前两种情况),于是听说了灰度计算,尝试了一下,即可跑出所有案例

最后是构造最后的n*n的数组,一开始的想法是从图片列表数组中按顺序插入数组中,后来通过先找左上角的图片(即左边上边无相邻图片)实现

#include<iostream>
#include<atlimage.h>
#include<cmath>
#include <io.h>
#include <string>
#include <vector>
#include <fstream>
using namespace std;
class Image{
public:
    CImage src;
    BYTE *pData;//存放所有的rgb
    BYTE **lineP = new BYTE *[4];//存放所有边上的rgb
    int width, height, bpp, pitch;//图片参数
    int next[4] = { -1,-1,-1,-1 };//上右下左四个方向的图片
    double **linePower;//四条边上与其他图片的权值
    void load(LPCTSTR file) {
        src = CImage();
        src.Load(file);//加载图片
        pData = (BYTE *)src.GetBits();//获取每个点

        //获取部分参数
        width = src.GetWidth();
        height = src.GetHeight();
        bpp = src.GetBPP();
        pitch = src.GetPitch();
        //加载边界
        loadLine();
    }
    void loadLine() {
        lineP[0] = new BYTE[width];//上边0
        lineP[1] = new BYTE[height];//右边1
        lineP[2] = new BYTE[width];//下边2
        lineP[3] = new BYTE[height];//左边3
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                int p[2] = { -1 ,-1};
                if (x == 0) {
                    p[0] = 3;//左边
                }else if (x == width - 1)
                {
                    p[0] = 1;//右边
                }
                if (y == 0){
                    p[1] = 0;//上边
                }else if (y == height - 1)
                {
                    p[1] = 2;//上边
                }
                //参数0 左右边 参数1 上下边
                for (int i = 0; i < 2; i++) 
                {
                    if (p[i] != -1) {
                        BYTE b = *(pData + pitch * y + x * bpp / 8 + 0);
                        BYTE g = *(pData + pitch * y + x * bpp / 8 + 1);
                        BYTE r = *(pData + pitch * y + x * bpp / 8 + 2);
                        lineP[p[i]][i == 0 ? y : x] = b * 0.114 + g * 0.587 + r * 0.299;
                    }
                }
            }
        }
    }
    
};
void join(LPCTSTR srcFilePath[], LPCTSTR destFilePath, int cnt)
{
    Image *srcImage = new Image[cnt];
    CImage destImage;
    int sqr = sqrt(cnt);//X*X

    for (int i = 0; i < cnt; i++)
    {
        srcImage[i].load(srcFilePath[i]);
        srcImage[i].linePower = new double *[cnt];//其他cnt张图片的边界权值
    }

    int height = srcImage[0].height, width = srcImage[0].width, bpp = srcImage[0].bpp;
    int length[4] = {width,height,width,height};//用于记录四条边的长度
    destImage.Create(width * sqr, height * sqr, bpp);//创建新的图片

    //计算每张图片每条边与别的图片的平方差
    for (int i = 0; i < cnt; i++)
    {
        for (int j = 0; j < cnt; j++) 
        {
            srcImage[i].linePower[j] = new double[4];//四条边的权值
            if (i == j) {
                for (int line = 0; line < 4; line++) 
                {
                    //-1为跳过标识
                    srcImage[i].next[line] = -1;
                }
                continue;//当前图片跳过本次循环
            }
            for (int line = 0; line < 4; line++) 
            {
                int otherLine = (line + 2) % 4;//0->2 1->3 2->0 3->1
                int count = 0;//平方差之和
                
                for (int point = 0; point < length[line]; point++) 
                {
                    count += pow(srcImage[i].lineP[line][point] - srcImage[j].lineP[otherLine][point], 2);
                }
                srcImage[i].linePower[j][line] = count / length[line];//平均平方差
            }
        }
        for (int line = 0; line < 4; line++) 
        {
            int min = -1;//标记
            for (int j = 0; j < cnt; j++) 
            {
                if (i == j) continue;//本张图片跳过
                if (min==-1 || srcImage[i].linePower[min][line] > srcImage[i].linePower[j][line]) {
                    min = j;//权值最小的图片下标
                }
            }
            srcImage[i].next[line] = min;//该条边上权值最小的图片
        }
    }
    
    //判断是否是彼此的唯一
    for (int i = 0; i < cnt; i++) 
    {
        for (int line = 0; line < 4; line++) 
        {
            if (srcImage[i].next[line] != -1) 
            {
                if (i != srcImage[srcImage[i].next[line]].next[(line + 2) % 4]) 
                {
                    srcImage[i].next[line] = -1;//不是彼此的唯一最小则打回原形
                }
            }
        }
    }
    /**
    string tip[4] = {"上","右","下","左"};
    for (int i = 0; i < cnt; i++) 
    {
        cout << "第" << i << "张图片" ;
        for (int line = 0; line < 4; line++) 
        {
            cout << endl<< tip[line] << srcImage[i].next[line] << '\t' << endl;
            for (int j = 0; j < cnt; j++) 
            {
                if (i == j) continue;
                cout << srcImage[i].linePower[j][line] << '\t';
            }
        }
        cout << endl;
    }**/
    
    int begin;//找到右下角的图片
    for (int i = 0; i < cnt; i++) 
    {
        if (srcImage[i].next[2] == -1 && srcImage[i].next[1] == -1) 
        {
            begin = i;
            break;
        }
    }
    //声明一个用于存储九宫格下标的二维数组
    int **despair = new int*[sqr];
    for (int i = 0; i < sqr; i++) {
        despair[i] = new int[sqr];
    }
    
    for (int i = sqr-1; i >=0; i--) {
        for (int j = sqr - 1; j >=0; j--) {
            if (i== sqr - 1) {
                if (j == sqr - 1) 
                { 
                    despair[i][j] = begin; //右下角赋值
                }
                else 
                {
                    despair[i][j] = srcImage[despair[i][j + 1]].next[0];//其余右边第一列
                }
            }
            else 
            {
                despair[i][j] = srcImage[despair[i + 1][j]].next[3];
            }
        }
    }
    for (int i = 0; i < sqr; i++) 
    {
        for (int j = 0; j < sqr; j++) 
        {
            cout << despair[j][i] << '\t';
        }
        cout << endl;
    }

    for (int i = 0; i < sqr; i++)
    {
        for (int j = 0; j < sqr; j++)
        {

            srcImage[despair[j][i]].src.Draw(destImage.GetDC(), width*j, height*i, width, height, 0, 0, width, height);
            destImage.ReleaseDC();
        }
    }
    destImage.Save(destFilePath);
}

/*
path: 指定目录
files: 保存结果
fileType: 指定的文件格式,如 .jpg
*/
void getAllFiles(string path, vector<string>& files, string fileType)
{
    //文件句柄
    long hFile = 0;
    //文件信息
    struct _finddata_t fileinfo;
    string p;
    if ((hFile = _findfirst(p.assign(path).append("\\*" + fileType).c_str(), &fileinfo)) != -1) {
        do {
            //保存文件的全路径
            files.push_back(p.assign(path).append("\\").append(fileinfo.name));
        } while (_findnext(hFile, &fileinfo) == 0); //寻找下一个,成功返回0,否则-1
        _findclose(hFile);
    }
}
//----------------------------------------------------------------------------------

int main()
{
    string fileName[4] = {"大作业第1组测试案例2x2","大作业第2组测试案例3x3" ,"大作业第3组测试案例6x6" ,"大作业第4组测试案例3x3" };
    string dir = "C:\\Users\\xjc82\\Desktop\\Day4\\";
    for (int i = 0; i < 4; i++) {
        vector<string> temp;

        getAllFiles(dir+fileName[i], temp, ".jpg");//读取图片列表
        int cnt = temp.size();//图片的数量

        //将图片地址String转为LPCTSTR
        LPCTSTR *srcFilePath = new LPCTSTR[cnt];
        CString *cs = new CString[cnt];
        for (int i = 0; i < cnt; i++)
        {
            cs[i] = temp[i].c_str();
            srcFilePath[i] = (LPCTSTR)cs[i];
        }
        //将导出图片地址String转为LPCTSTR
        CString resultDir = (dir + fileName[i] + ".jpg").c_str();
        LPCTSTR destFilePath = (LPCTSTR)resultDir;
        join(srcFilePath, destFilePath, cnt);
    }
    return 0;
}

案例的图片素材:案例.rar