2年前在学习图像算法的时候看到一个文档倾斜矫正的算法。

也就是说能将一些文档图像进行旋转矫正,

当然这个算法一般用于一些文档扫描软件做后处理

或者用于ocr 文字识别做前处理。

相关的关键词: 抗倾斜 反倾斜  Deskew 等等。

最简单算法实现思路,采用 霍夫变换(Hough Transform)进行直线检测,

当然也可以用霍夫变换检测圆。

在倾斜矫正算法中,自然就是检测直线。

通过对检测出来的直线进行角度判断,

一般取 认可度最高的几条直线进行计算,

最后求取均衡后的角度值。

进行图像角度的旋转即可。

大概算法步骤如下:

1.转换为灰度图

2.判断是否为文本图片,如果不是进行 进行 反相操作

3.检测直线,进行角度判断

4.通过角度进行图像旋转

这么一个基本思路,当然想要检测得更加精准,

可以做一些文本区域判断,图像修复增强之类的前处理操作。

最近有点强迫症犯了,开始回归本源,强迫自己用c语言来实现,

fastsin以及fastcos 来自 arm公司的开源项目。

霍夫变换相关算法原理,请移步 百度 google 维基百科。

或直接看代码实现,可了悟于心。

有事没事,多看看业内大公司的开源项目,

萝卜白菜都有,重点是学习其思路。

嗯,有些网友可能会说,opencv一两行代码就可以做到了。

对的,一些sdk,api,开源框架一两句代码是做到了,

知道,用到,与真正做到,这是两条路。

我只想说一句,愿世界和平。

附完整代码:

//如果是Windows的话,调用系统API ShellExecuteA打开图片
#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#define USE_SHELL_OPEN
#endif

#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
//ref:https://github.com/nothings/stb/blob/master/stb_image.h
#define TJE_IMPLEMENTATION
#include "tiny_jpeg.h"
//ref:https://github.com/serge-rgb/TinyJPEG/blob/master/tiny_jpeg.h
#include <math.h>
#include <io.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>

//计时
#include <stdint.h>
#if   defined(__APPLE__)
# include <mach/mach_time.h>
#elif defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else // __linux
# include <time.h>
# ifndef  CLOCK_MONOTONIC //_RAW
#  define CLOCK_MONOTONIC CLOCK_REALTIME
# endif
#endif
static
uint64_t nanotimer() {
    ;
#if defined(__APPLE__)
    static mach_timebase_info_data_t frequency;
    if (!ever) {
        if (mach_timebase_info(&frequency) != KERN_SUCCESS) {
            ;
        }
        ever = ;
    }
    return;
#elif defined(_WIN32)
    static LARGE_INTEGER frequency;
    if (!ever) {
        QueryPerformanceFrequency(&frequency);
        ever = ;
    }
    LARGE_INTEGER t;
    QueryPerformanceCounter(&t);
    return (t.QuadPart * (uint64_t)1e9) / frequency.QuadPart;
#else // __linux
    struct timespec t;
    if (!ever) {
        ) {
            ;
        }
        ever = ;
    }
    clock_gettime(CLOCK_MONOTONIC, &spec);
    return (t.tv_sec * (uint64_t)1e9) + t.tv_nsec;
#endif
}

static double now()
{
    ;
    if (!epoch) {
        epoch = nanotimer();
    }
    return (nanotimer() - epoch) / 1e9;
};

double  calcElapsed(double start, double end)
{
    double took = -start;
    return took + end;
}

//存储当前传入文件位置的变量
];
//加载图片
unsigned char * loadImage(const char *filename, int *Width, int *Height, int *Channels)
{
    );
}
//保存图片
void saveImage(const char *filename, int Width, int Height, int Channels, unsigned char *Output)
{

    memcpy(saveFile + strlen(saveFile), filename, strlen(filename));
    *(saveFile + strlen(saveFile) + ) = ;
    //保存为jpg
    if (!tje_encode_to_file(saveFile, Width, Height, Channels, true, Output))
    {
        fprintf(stderr, "写入 JPEG 文件失败.\n");
        return;
    }

#ifdef USE_SHELL_OPEN
    ShellExecuteA(NULL, "open", saveFile, NULL, NULL, SW_SHOW);
#else
    //其他平台暂不实现
#endif
}

#ifndef ClampToByte
#define  ClampToByte(  v )  ( ((unsigned)(int)(v)) <(255) ? (v) : ((int)(v) < 0) ? (0) : (255))
#endif 

#define M_PI 3.14159265358979323846f

typedef struct cpu_HoughLine
{
    float Theta;
    int Radius;
    int Intensity;
    float RelativeIntensity;
} cpu_HoughLine;

typedef struct cpu_rect
{
    int  x;
    int  y;
    int  Width;
    int  Height;
} cpu_rect;

#ifndef clamp
#define clamp(value,min,max)  ((value) > (max )? (max ): (value) < (min) ? (min) : (value))
#endif 

#define FAST_MATH_TABLE_SIZE  512

] = {
    0.00000000f, 0.01227154f, 0.02454123f, 0.03680722f, 0.04906767f, 0.06132074f,
    0.07356456f, 0.08579731f, 0.09801714f, 0.11022221f, 0.12241068f, 0.13458071f,
    0.14673047f, 0.15885814f, 0.17096189f, 0.18303989f, 0.19509032f, 0.20711138f,
    0.21910124f, 0.23105811f, 0.24298018f, 0.25486566f, 0.26671276f, 0.27851969f,
    0.29028468f, 0.30200595f, 0.31368174f, 0.32531029f, 0.33688985f, 0.34841868f,
    0.35989504f, 0.37131719f, 0.38268343f, 0.39399204f, 0.40524131f, 0.41642956f,
    0.42755509f, 0.43861624f, 0.44961133f, 0.46053871f, 0.47139674f, 0.48218377f,
    0.49289819f, 0.50353838f, 0.51410274f, 0.52458968f, 0.53499762f, 0.54532499f,
    0.55557023f, 0.56573181f, 0.57580819f, 0.58579786f, 0.59569930f, 0.60551104f,
    0.61523159f, 0.62485949f, 0.63439328f, 0.64383154f, 0.65317284f, 0.66241578f,
    0.67155895f, 0.68060100f, 0.68954054f, 0.69837625f, 0.70710678f, 0.71573083f,
    0.72424708f, 0.73265427f, 0.74095113f, 0.74913639f, 0.75720885f, 0.76516727f,
    0.77301045f, 0.78073723f, 0.78834643f, 0.79583690f, 0.80320753f, 0.81045720f,
    0.81758481f, 0.82458930f, 0.83146961f, 0.83822471f, 0.84485357f, 0.85135519f,
    0.85772861f, 0.86397286f, 0.87008699f, 0.87607009f, 0.88192126f, 0.88763962f,
    0.89322430f, 0.89867447f, 0.90398929f, 0.90916798f, 0.91420976f, 0.91911385f,
    0.92387953f, 0.92850608f, 0.93299280f, 0.93733901f, 0.94154407f, 0.94560733f,
    0.94952818f, 0.95330604f, 0.95694034f, 0.96043052f, 0.96377607f, 0.96697647f,
    0.97003125f, 0.97293995f, 0.97570213f, 0.97831737f, 0.98078528f, 0.98310549f,
    0.98527764f, 0.98730142f, 0.98917651f, 0.99090264f, 0.99247953f, 0.99390697f,
    0.99518473f, 0.99631261f, 0.99729046f, 0.99811811f, 0.99879546f, 0.99932238f,
    0.99969882f, 0.99992470f, 1.00000000f, 0.99992470f, 0.99969882f, 0.99932238f,
    0.99879546f, 0.99811811f, 0.99729046f, 0.99631261f, 0.99518473f, 0.99390697f,
    0.99247953f, 0.99090264f, 0.98917651f, 0.98730142f, 0.98527764f, 0.98310549f,
    0.98078528f, 0.97831737f, 0.97570213f, 0.97293995f, 0.97003125f, 0.96697647f,
    0.96377607f, 0.96043052f, 0.95694034f, 0.95330604f, 0.94952818f, 0.94560733f,
    0.94154407f, 0.93733901f, 0.93299280f, 0.92850608f, 0.92387953f, 0.91911385f,
    0.91420976f, 0.90916798f, 0.90398929f, 0.89867447f, 0.89322430f, 0.88763962f,
    0.88192126f, 0.87607009f, 0.87008699f, 0.86397286f, 0.85772861f, 0.85135519f,
    0.84485357f, 0.83822471f, 0.83146961f, 0.82458930f, 0.81758481f, 0.81045720f,
    0.80320753f, 0.79583690f, 0.78834643f, 0.78073723f, 0.77301045f, 0.76516727f,
    0.75720885f, 0.74913639f, 0.74095113f, 0.73265427f, 0.72424708f, 0.71573083f,
    0.70710678f, 0.69837625f, 0.68954054f, 0.68060100f, 0.67155895f, 0.66241578f,
    0.65317284f, 0.64383154f, 0.63439328f, 0.62485949f, 0.61523159f, 0.60551104f,
    0.59569930f, 0.58579786f, 0.57580819f, 0.56573181f, 0.55557023f, 0.54532499f,
    0.53499762f, 0.52458968f, 0.51410274f, 0.50353838f, 0.49289819f, 0.48218377f,
    0.47139674f, 0.46053871f, 0.44961133f, 0.43861624f, 0.42755509f, 0.41642956f,
    0.40524131f, 0.39399204f, 0.38268343f, 0.37131719f, 0.35989504f, 0.34841868f,
    0.33688985f, 0.32531029f, 0.31368174f, 0.30200595f, 0.29028468f, 0.27851969f,
    0.26671276f, 0.25486566f, 0.24298018f, 0.23105811f, 0.21910124f, 0.20711138f,
    0.19509032f, 0.18303989f, 0.17096189f, 0.15885814f, 0.14673047f, 0.13458071f,
    0.12241068f, 0.11022221f, 0.09801714f, 0.08579731f, 0.07356456f, 0.06132074f,
    0.04906767f, 0.03680722f, 0.02454123f, 0.01227154f, 0.00000000f, -0.01227154f,
    -0.02454123f, -0.03680722f, -0.04906767f, -0.06132074f, -0.07356456f,
    -0.08579731f, -0.09801714f, -0.11022221f, -0.12241068f, -0.13458071f,
    -0.14673047f, -0.15885814f, -0.17096189f, -0.18303989f, -0.19509032f,
    -0.20711138f, -0.21910124f, -0.23105811f, -0.24298018f, -0.25486566f,
    -0.26671276f, -0.27851969f, -0.29028468f, -0.30200595f, -0.31368174f,
    -0.32531029f, -0.33688985f, -0.34841868f, -0.35989504f, -0.37131719f,
    -0.38268343f, -0.39399204f, -0.40524131f, -0.41642956f, -0.42755509f,
    -0.43861624f, -0.44961133f, -0.46053871f, -0.47139674f, -0.48218377f,
    -0.49289819f, -0.50353838f, -0.51410274f, -0.52458968f, -0.53499762f,
    -0.54532499f, -0.55557023f, -0.56573181f, -0.57580819f, -0.58579786f,
    -0.59569930f, -0.60551104f, -0.61523159f, -0.62485949f, -0.63439328f,
    -0.64383154f, -0.65317284f, -0.66241578f, -0.67155895f, -0.68060100f,
    -0.68954054f, -0.69837625f, -0.70710678f, -0.71573083f, -0.72424708f,
    -0.73265427f, -0.74095113f, -0.74913639f, -0.75720885f, -0.76516727f,
    -0.77301045f, -0.78073723f, -0.78834643f, -0.79583690f, -0.80320753f,
    -0.81045720f, -0.81758481f, -0.82458930f, -0.83146961f, -0.83822471f,
    -0.84485357f, -0.85135519f, -0.85772861f, -0.86397286f, -0.87008699f,
    -0.87607009f, -0.88192126f, -0.88763962f, -0.89322430f, -0.89867447f,
    -0.90398929f, -0.90916798f, -0.91420976f, -0.91911385f, -0.92387953f,
    -0.92850608f, -0.93299280f, -0.93733901f, -0.94154407f, -0.94560733f,
    -0.94952818f, -0.95330604f, -0.95694034f, -0.96043052f, -0.96377607f,
    -0.96697647f, -0.97003125f, -0.97293995f, -0.97570213f, -0.97831737f,
    -0.98078528f, -0.98310549f, -0.98527764f, -0.98730142f, -0.98917651f,
    -0.99090264f, -0.99247953f, -0.99390697f, -0.99518473f, -0.99631261f,
    -0.99729046f, -0.99811811f, -0.99879546f, -0.99932238f, -0.99969882f,
    -0.99992470f, -1.00000000f, -0.99992470f, -0.99969882f, -0.99932238f,
    -0.99879546f, -0.99811811f, -0.99729046f, -0.99631261f, -0.99518473f,
    -0.99390697f, -0.99247953f, -0.99090264f, -0.98917651f, -0.98730142f,
    -0.98527764f, -0.98310549f, -0.98078528f, -0.97831737f, -0.97570213f,
    -0.97293995f, -0.97003125f, -0.96697647f, -0.96377607f, -0.96043052f,
    -0.95694034f, -0.95330604f, -0.94952818f, -0.94560733f, -0.94154407f,
    -0.93733901f, -0.93299280f, -0.92850608f, -0.92387953f, -0.91911385f,
    -0.91420976f, -0.90916798f, -0.90398929f, -0.89867447f, -0.89322430f,
    -0.88763962f, -0.88192126f, -0.87607009f, -0.87008699f, -0.86397286f,
    -0.85772861f, -0.85135519f, -0.84485357f, -0.83822471f, -0.83146961f,
    -0.82458930f, -0.81758481f, -0.81045720f, -0.80320753f, -0.79583690f,
    -0.78834643f, -0.78073723f, -0.77301045f, -0.76516727f, -0.75720885f,
    -0.74913639f, -0.74095113f, -0.73265427f, -0.72424708f, -0.71573083f,
    -0.70710678f, -0.69837625f, -0.68954054f, -0.68060100f, -0.67155895f,
    -0.66241578f, -0.65317284f, -0.64383154f, -0.63439328f, -0.62485949f,
    -0.61523159f, -0.60551104f, -0.59569930f, -0.58579786f, -0.57580819f,
    -0.56573181f, -0.55557023f, -0.54532499f, -0.53499762f, -0.52458968f,
    -0.51410274f, -0.50353838f, -0.49289819f, -0.48218377f, -0.47139674f,
    -0.46053871f, -0.44961133f, -0.43861624f, -0.42755509f, -0.41642956f,
    -0.40524131f, -0.39399204f, -0.38268343f, -0.37131719f, -0.35989504f,
    -0.34841868f, -0.33688985f, -0.32531029f, -0.31368174f, -0.30200595f,
    -0.29028468f, -0.27851969f, -0.26671276f, -0.25486566f, -0.24298018f,
    -0.23105811f, -0.21910124f, -0.20711138f, -0.19509032f, -0.18303989f,
    -0.17096189f, -0.15885814f, -0.14673047f, -0.13458071f, -0.12241068f,
    -0.11022221f, -0.09801714f, -0.08579731f, -0.07356456f, -0.06132074f,
    -0.04906767f, -0.03680722f, -0.02454123f, -0.01227154f, -0.00000000f
};

inline float  fastSin(
    float x)
{
    float sinVal, fract, in;
    unsigned short  index;
    float a, b;
    int n;
    float findex;

    in = x * 0.159154943092f;

    n = (int)in;

    if (x < 0.0f)
    {
        n--;
    }

    in = in - (float)n;

    findex = (float)FAST_MATH_TABLE_SIZE * in;
    if (findex >= 512.0f) {
        findex -= 512.0f;
    }

    index = ((unsigned short)findex) & 0x1ff;

    fract = findex - (float)index;

    a = sinTable_f32[index];
    b = sinTable_f32[index + ];

    sinVal = (1.0f - fract)*a + fract*b;

    return (sinVal);
}

inline float  fastCos(
    float x)
{
    float cosVal, fract, in;
    unsigned short index;
    float a, b;
    int n;
    float findex;

    in = x * 0.159154943092f + 0.25f;

    n = (int)in;

    if (in < 0.0f)
    {
        n--;
    }

    in = in - (float)n;

    findex = (float)FAST_MATH_TABLE_SIZE * in;
    index = ((unsigned short)findex) & 0x1ff;

    fract = findex - (float)index;

    a = sinTable_f32[index];
    b = sinTable_f32[index + ];

    cosVal = (1.0f - fract)*a + fract*b;

    return (cosVal);
}

void CPUImageGrayscaleFilter(unsigned char* Input, unsigned char* Output, int  Width, int  Height, int Stride)
{
    int Channels = Stride / Width;

     + 0.5);
     + 0.5);
     - B_WT - G_WT;            //     int(0.299 * 256 + 0.5);
    int Channel = Stride / Width;
    )
    {
        ; Y < Height; Y++)
        {
            unsigned char *LinePS = Input + Y * Stride;
            unsigned char *LinePD = Output + Y * Width;
            ;
            ; X += , LinePS += Channel * )
            {
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
            }
            for (; X < Width; X++, LinePS += Channel)
            {
                LinePD[X] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
            }
        }
    }
    )
    {
        ; Y < Height; Y++)
        {
            unsigned char *LinePS = Input + Y * Stride;
            unsigned char *LinePD = Output + Y * Width;
            ;
            ; X += , LinePS += Channel * )
            {
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
                LinePD[X + ] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
            }
            for (; X < Width; X++, LinePS += Channel)
            {
                LinePD[X] = (B_WT * LinePS[] + G_WT * LinePS[] + R_WT * LinePS[]) >> ;
            }
        }
    }
    )
    {
        if (Output != Input)
        {
            memcpy(Output, Input, Height*Stride);
        }
    }
}

void CPUImageColorInvertFilter(unsigned char* Input, unsigned char* Output, int  Width, int  Height, int Stride)
{
    ] = {  };
    ; pixel < ; pixel++)
    {
        invertMap[pixel] = ( - pixel);
    }
    ) {
        ; Y < Height; Y++)
        {
            unsigned char*     pOutput = Output + (Y * Stride);
            unsigned char*     pInput = Input + (Y * Stride);
            ; X < Width; X++)
            {
                pOutput[X] = invertMap[pInput[X]];
            }
        }
    }
    else
    {

        ; Y < Height; Y++)
        {
            unsigned char*     pOutput = Output + (Y * Stride);
            unsigned char*     pInput = Input + (Y * Stride);
            ; X < Width; X++)
            {
                pOutput[] = invertMap[pInput[]];
                pOutput[] = invertMap[pInput[]];
                pOutput[] = invertMap[pInput[]];
                pInput += Channels;
                pOutput += Channels;
            }
        }
    }
}
float  CPUImageCalcSkewAngle(unsigned char* Input, int Width, int Height, cpu_rect *CheckRectPtr, int maxSkewToDetect, int stepsPerDegree, int localPeakRadius, int nLineCount)
{
    cpu_rect CheckRect = *CheckRectPtr;
    //确定指定的区域在原图片范围内
    CheckRect.x = clamp(CheckRect.x, , Width - );
    CheckRect.y = clamp(CheckRect.y, , Height - );
    CheckRect.Width = clamp(CheckRect.Width, , Width - );
    CheckRect.Height = clamp(CheckRect.Height, , Height - );

    // 处理参数
    maxSkewToDetect = clamp(maxSkewToDetect, , );
    localPeakRadius = clamp(localPeakRadius, , );
    stepsPerDegree = clamp(stepsPerDegree, , );
     * maxSkewToDetect * stepsPerDegree);
     * maxSkewToDetect * M_PI / ) / houghHeight;
    ;
    ;
    // 计算 Hough 映射宽度
    int halfHoughWidth = (int)sqrtf((float)(halfWidth * halfWidth + halfHeight * halfHeight));
    );
    float minTheta = 90.0f - maxSkewToDetect;
    unsigned short * houghMap = (unsigned short *)calloc(houghHeight*houghWidth, sizeof(unsigned short));
    float* sinMap = (float*)malloc(houghHeight * sizeof(float));
    float* cosMap = (float*)malloc(houghHeight * sizeof(float));
    cpu_HoughLine* HoughLines = (cpu_HoughLine*)calloc(houghHeight*houghWidth, sizeof(cpu_HoughLine));
    if (houghMap == NULL || sinMap == NULL || cosMap == NULL || HoughLines == NULL)
    {
        if (houghMap)
        {
            free(houghMap);
            houghMap = NULL;
        }
        if (sinMap)
        {
            free(sinMap);
            sinMap = NULL;
        }
        if (cosMap)
        {
            free(cosMap);
            cosMap = NULL;
        }
        if (HoughLines)
        {
            free(HoughLines);
            HoughLines = NULL;
        }
        return 0.0f;
    }
    else
    {
        // 预计算 Sin 与 Cos表
        float mt = (minTheta * M_PI / 180.0f);
        ; i < houghHeight; i++)
        {
            float cur_weight = mt + (i * thetaStep);
            sinMap[i] = fastSin(cur_weight);
            cosMap[i] = fastCos(cur_weight);
        }
    }
    int startX = -halfWidth + CheckRect.x;
    int startY = -halfHeight + CheckRect.y;
    int stopX = Width - halfWidth - (Width - CheckRect.Width);
    ;
    int offset = Width - CheckRect.Width;

    unsigned char* src = Input + CheckRect.y *  Width + CheckRect.x;
    unsigned char* srcBelow = src + Width;

    for (int Y = startY; Y < stopY; Y++)
    {
        for (int X = startX; X < stopX; X++, src++, srcBelow++)
        {
            ) && (*srcBelow >= ))
            {
                ; theta < houghHeight; theta++)
                {
                    int radius = (int)(cosMap[theta] * X - sinMap[theta] * Y) + halfHoughWidth;

                    ) || (radius >= houghWidth))
                    {
                        continue;
                    }

                    houghMap[theta*houghWidth + radius]++;
                }
            }
        }
        src += offset;
        srcBelow += offset;
    }

    // 找到 Hough映射的最大值
    float maxMapIntensity = 0.0000000001f;
    ; theta < houghHeight; theta++)
    {
        unsigned short * houghMapLine = houghMap + theta*houghWidth;
        ; radius < houghWidth; radius++)
        {
            maxMapIntensity = max(maxMapIntensity, houghMapLine[radius]);
        }
    }
    ;

    // 收集大于或等于指定强度的直线

    ;
    bool foundGreater = false;
    ;
    ; theta < houghHeight; theta++)
    {
        unsigned short * houghMapLine = houghMap + theta*houghWidth;
        ; radius < houghWidth; radius++)
        {
            // 取当前强度
            lineIntensity = houghMapLine[radius];

            if (lineIntensity < minLineIntensity)
            {
                continue;
            }

            foundGreater = false;

            // 检查邻边
            for (int t = theta - localPeakRadius, ttMax = theta + localPeakRadius; t < ttMax; t++)
            {
                //跳过map值
                )
                {
                    continue;
                }
                if (t >= houghHeight)
                {
                    break;
                }

                //如果不是局部最大则跳出
                if (foundGreater == true)
                {
                    break;
                }
                for (int r = radius - localPeakRadius, trMax = radius + localPeakRadius; r < trMax; r++)
                {
                    //跳过map值
                    )
                    {
                        continue;
                    }
                    if (r >= houghWidth)
                    {
                        break;
                    }
                    // 当前值与邻边对比
                    if (houghMap[t*houghWidth + r] > lineIntensity)
                    {
                        foundGreater = true;
                        break;
                    }
                }
            }
            // 可能是局部最大值,记录下来
            if (!foundGreater)
            {
                cpu_HoughLine tempVar;
                tempVar.Theta = 90.0f - maxSkewToDetect + (theta) / stepsPerDegree;
                tempVar.Radius = (radius - halfHoughWidth);
                tempVar.Intensity = lineIntensity;
                tempVar.RelativeIntensity = lineIntensity / maxMapIntensity;
                HoughLines[lineSize] = tempVar;
                lineSize++;
            }
        }
    }

    ;
    )
    {
        //排序,从大到小
        cpu_HoughLine temp;
        ; i < lineSize; i++)
        {
            ; j < lineSize - ; j++)
            {
                ].Intensity)
                {
                    temp = HoughLines[j + ];
                    HoughLines[j + ] = HoughLines[j];
                    HoughLines[j] = temp;
                }
            }
        }

        int n = min(nLineCount, lineSize);

        ;

        ; i < n; i++)
        {
            if (HoughLines[i].RelativeIntensity > 0.5f)
            {
                skewAngle += (HoughLines[i].Theta * HoughLines[i].RelativeIntensity);
                sumIntensity += HoughLines[i].RelativeIntensity;
            }
        }
        skewAngle = skewAngle / sumIntensity;
    }
    if (houghMap)
    {
        free(houghMap);
        houghMap = NULL;
    }
    if (sinMap)
    {
        free(sinMap);
        sinMap = NULL;
    }
    if (cosMap)
    {
        free(cosMap);
        cosMap = NULL;
    }
    if (HoughLines)
    {
        free(HoughLines);
        HoughLines = NULL;
    }
    )
    {
        return skewAngle - 90.0f;
    }
    return skewAngle;
}

void CPUImageRotateBilinear(unsigned char * Input, int Width, int Height, int Stride, unsigned char * Output, int outWidth, int outHeight, float angle, bool keepSize, int fillColorR, int fillColorG, int fillColorB)
{
    if (Input == NULL || Output == NULL) return;

    ) / ;
    ) / ;

    // 输出图像的半径大小

    ) / ;
    ) / ;

    // 角度的正弦和余弦
    float angleRad = -angle * M_PI / 180.0f;
    float angleCos = fastCos(angleRad);
    float angleSin = fastSin(angleRad);
    int Channels = Stride / Width;
    ) ? outWidth : outWidth * Channels);

    // 背景色
    unsigned char fillR = fillColorR;
    unsigned char fillG = fillColorG;
    unsigned char fillB = fillColorB;
    // 临界点
    ;
    ;
    // 四点指针
    unsigned char* src = (unsigned char*)Input;
    unsigned char* dst = (unsigned char*)Output;
    // cx, cy  目标像素的相对于图像中心的坐标
    )
    {
        float cy = -newYradius;
        ; y < outHeight; y++)
        {
            const     float    tx = angleSin * cy + oldXradius;
            const float    ty = angleCos * cy + oldYradius;

            float cx = -newXradius;
            ; x < outWidth; x++, dst++)
            {
                // 初始起点位置
                const     float    ox = tx + angleCos * cx;
                const     float    oy = ty - angleSin * cx;

                const int    ox1 = (int)ox;
                const int    oy1 = (int)oy;

                // 判断是否为有效区域
                ) || (oy1 < ) || (ox1 >= Width) || (oy1 >= Height))
                {
                    // 无效区域填充背景
                    *dst = fillG;
                }
                else
                {
                    // 边界点处理
                    ;
                    ;
                    float dx1 = ox - (float)ox1;
                    )
                        dx1 = ;
                    const     float dx2 = 1.0f - dx1;
                    float dy1 = oy - (float)oy1;
                    )
                        dy1 = ;
                    const     float dy2 = 1.0f - dy1;

                    unsigned char*p1 = src + oy1 * Stride;
                    unsigned char*    p2 = src + oy2 * Stride;
                    // 进行四点插值
                    *dst = (unsigned char)(
                        dy2 * (dx2 * p1[ox1] + dx1 * p1[ox2]) +
                        dy1 * (dx2 * p2[ox1] + dx1 * p2[ox2]));
                }
                cx++;
            }
            cy++;
            dst += dstOffset;
        }
    }
    else
    {
        float cy = -newYradius;
        ; y < outHeight; y++)
        {
            const     float     tx = angleSin * cy + oldXradius;
            const     float     ty = angleCos * cy + oldYradius;

            float cx = -newXradius;
            ; x < outWidth; x++, dst += Channels)
            {
                // 初始起点位置
                const     float ox = tx + angleCos * cx;
                const     float oy = ty - angleSin * cx;
                const int    ox1 = (int)ox;
                const int    oy1 = (int)oy;

                // 判断是否为有效区域
                ) || (oy1 < ) || (ox1 >= Width) || (oy1 >= Height))
                {
                    // 无效区域填充背景
                    dst[] = fillR;
                    dst[] = fillG;
                    dst[] = fillB;
                }
                else
                {
                    // 边界点处理
                    ;
                    ;
                    float dx1 = ox - (float)ox1;
                    )
                        dx1 = ;
                    const    float dx2 = 1.0f - dx1;
                    float dy1 = oy - (float)oy1;
                    )
                        dy1 = ;
                    const    float    dy2 = 1.0f - dy1;

                    // 计算四点的坐标
                    unsigned char*    p1 = src + oy1 * Stride;
                    unsigned char*  p2 = p1;
                    p1 += ox1 * Channels;
                    p2 += ox2 * Channels;

                    unsigned char* p3 = src + oy2 * Stride;
                    unsigned char* p4 = p3;
                    p3 += ox1 * Channels;
                    p4 += ox2 * Channels;

                    // 进行四点插值
                    dst[] = (unsigned char)(
                        dy2 * (dx2 * p1[] + dx1 * p2[]) +
                        dy1 * (dx2 * p3[] + dx1 * p4[]));
                    dst[] = (unsigned char)(
                        dy2 * (dx2 * p1[] + dx1 * p2[]) +
                        dy1 * (dx2 * p3[] + dx1 * p4[]));
                    dst[] = (unsigned char)(
                        dy2 * (dx2 * p1[] + dx1 * p2[]) +
                        dy1 * (dx2 * p3[] + dx1 * p4[]));
                }
                cx++;
            }
            cy++;
            dst += dstOffset;
        }
    }
}

bool CPUImageIsTextImage(unsigned char * Input, int Width, int Height)
{
    ;
    ;
    ;

    ];
    ];

    ; i < ; i++)
    {
        cur_color[i] = ;
        prev_color[i] = ;
    }

    ; i <= blacklimit; i++)
    {
        //黑色
        cur_color[i] = ;
        prev_color[i] = ;
    }

     + contrast_offset; i <= greylimit; i++)
    {
        //灰色
        cur_color[i] = ;
        prev_color[i] = ;
    }

     + contrast_offset; i <= ; i++)
    {
        //白色
        cur_color[i] = ;
        prev_color[i] = ;
    }
    ;

    ;
    ; y < Height; y += )
    {
        n++;
        ;
        unsigned char *  buffer = Input + y*Width;
        ;
        ; x < Width; x++)
        {
            )];
            const unsigned char     cur_pixel = buffer[x];

            if ((prev_color[prev_pixel]) && (cur_color[cur_pixel]))
            {
                //是否是白色
                )
                {
                    white_amt++;
                }
            }
        }
        //白色的一行
        if (((float)white_amt / (float)x) > 0.85f)
        {
            line_count++;
        }
    }

    .f) ? (float)line_count / (float)n : 0.0f;

    if (line_count_ratio < 0.4f || line_count_ratio > 1.0f)
    {
        return false;
    }

    return true;
}

bool     CPUImageDocumentDeskew(unsigned char * Input, unsigned char *Output, int Width, int Height, int Stride)
{
    if (Input == NULL || Output == NULL || Input == Output)
        return false;
    int Channels = Stride / Width;
    //最大倾斜角度
    ;

    cpu_rect rect = {  };
    rect.Width = Width;
    rect.Height = Height;
    // 以最大权重的2条直线为基准计算倾斜角度
    ;
    //角度步进数
    ;
    //局部临界半径
    ;
    CPUImageGrayscaleFilter(Input, Output, Width, Height, Stride);
    if (!CPUImageIsTextImage(Output, Width, Height))
    {
        CPUImageColorInvertFilter(Output, Output, Width, Height, Width);
    }
    float skewAngle = CPUImageCalcSkewAngle(Output, Width, Height, &rect, maxSkewToDetect, stepsPerDegree, localPeakRadius, nLineCount);
    ) || (skewAngle < -maxSkewToDetect || skewAngle >   maxSkewToDetect))
    {
        memcpy(Output, Input, Height* Stride * sizeof(unsigned char));
        return false;
    }
    else
    {
        CPUImageRotateBilinear(Input, Width, Height, Stride, Output, Width, Height, -skewAngle, , , );
    }
    return true;
}

//分割路径函数
void splitpath(const char* path, char* drv, char* dir, char* name, char* ext)
{
    const char* end;
    const char* p;
    const char* s;
    ] && path[] == ':') {
        if (drv) {
            *drv++ = *path++;
            *drv++ = *path++;
            *drv = '\0';
        }
    }
    else if (drv)
        *drv = '\0';
    for (end = path; *end && *end != ':';)
        end++;
    for (p = end; p > path && *--p != '\\' && *p != '/';)
        if (*p == '.') {
            end = p;
            break;
        }
    if (ext)
        for (s = end; (*ext = *s++);)
            ext++;
    for (p = end; p > path;)
        if (*--p == '\\' || *p == '/') {
            p++;
            break;
        }
    if (name) {
        for (s = p; s < end;)
            *name++ = *s++;
        *name = '\0';
    }
    if (dir) {
        for (s = path; s < p;)
            *dir++ = *s++;
        *dir = '\0';
    }
}

//取当前传入的文件位置
void getCurrentFilePath(const char *filePath, char *saveFile)
{
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char fname[_MAX_FNAME];
    char ext[_MAX_EXT];
    splitpath(filePath, drive, dir, fname, ext);
    int n = strlen(filePath);
    memcpy(saveFile, filePath, n);
    char * cur_saveFile = saveFile + (n - strlen(ext));
    cur_saveFile[] = '_';
    cur_saveFile[] = ;
}

int main(int argc, char **argv)
{
    printf("Image Processing \n ");
    printf("博客:http://tntmonks.cnblogs.com/ \n ");
    printf("支持解析如下图片格式: \n ");
    printf("JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC \n ");

    //检查参数是否正确

    )
    {
        printf("参数错误。 \n ");
        printf("请拖放文件到可执行文件上,或使用命令行:imageProc.exe 图片 \n ");
        printf("请拖放文件例如: imageProc.exe d:\\image.jpg \n ");

        ;
    }

    ];
    //检查输入的文件是否存在
    ) == -)
    {
        printf("输入的文件不存在,参数错误! \n ");
    }

    getCurrentFilePath(szfile, saveFile);

    ;                    //图片宽度
    ;                   //图片高度
    ;                 //图片通道数
    unsigned char *inputImage = NULL; //输入图片指针
    double startTime = now();
    //加载图片
    inputImage = loadImage(szfile, &Width, &Height, &Channels);

    double nLoadTime = calcElapsed(startTime, now());
    printf());
    ) && (Width != ) && (Height != ))
    {
        //分配与载入同等内存用于处理后输出结果
        unsigned char *outputImg = (unsigned char *)stbi__malloc(Width * Channels * Height * sizeof(unsigned char));
        if (inputImage)
        {
            //如果图片加载成功,则将内容复制给输出内存,方便处理
            memcpy(outputImg, inputImage, Width * Channels * Height);
        }
        else
        {
            printf("加载文件: %s 失败!\n ", szfile);
        }
        startTime = now();
        //处理算法
        CPUImageDocumentDeskew(inputImage, outputImg, Width, Height, Width*Channels);
        double nProcessTime = calcElapsed(startTime, now());
        printf());
        //保存处理后的图片
        startTime = now();

        saveImage("_done.jpg", Width, Height, Channels, outputImg);
        double nSaveTime = calcElapsed(startTime, now());

        printf());
        //释放占用的内存
        if (outputImg)
        {
            stbi_image_free(outputImg);
            outputImg = NULL;
        }

        if (inputImage)
        {
            stbi_image_free(inputImage);
            inputImage = NULL;
        }
    }
    else
    {
        printf("加载文件: %s 失败!\n", szfile);
    }

    getchar();
    printf("按任意键退出程序 \n");

    return EXIT_SUCCESS;
}

项目地址:https://github.com/cpuimage/deskew

贴上几张效果图.

以上,权当抛砖引玉。

若有其他相关问题或者需求也可以邮件联系俺探讨。

邮箱地址是: 
gaozhihan@vip.qq.com

图片文档倾斜矫正算法 附完整c代码的更多相关文章

  1. mser 最大稳定极值区域(文字区域定位)算法 附完整C代码

    mser 的全称:Maximally Stable Extremal Regions 第一次听说这个算法时,是来自当时部门的一个同事, 提及到他的项目用它来做文字区域的定位,对这个算法做了一些优化. ...

  2. 音频降噪算法 附完整C代码

    降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法和音频算法 都有其共通点. 图像是偏向 空间 处理,例如图片中的某个区域. 图像很多时候是以二维数据 ...

  3. 自动曝光修复算法 附完整C代码

    众所周知, 图像方面的3A算法有: AF自动对焦(Automatic Focus)自动对焦即调节摄像头焦距自动得到清晰的图像的过程 AE自动曝光(Automatic Exposure)自动曝光的是为了 ...

  4. 基于RNN的音频降噪算法 (附完整C代码)

    前几天无意间看到一个项目rnnoise. 项目地址: https://github.com/xiph/rnnoise 基于RNN的音频降噪算法. 采用的是 GRU/LSTM 模型. 阅读下训练代码,可 ...

  5. 音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

  6. 音频自动增益 与 静音检测 算法 附完整C代码【转】

    转自:https://www.cnblogs.com/cpuimage/p/8908551.html 前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用 ...

  7. 基于傅里叶变换的音频重采样算法 (附完整c代码)

    前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲, ...

  8. 3D Lut 电影级调色算法 附完整C代码

    在前面的文章,我提到过VSCO Cam 的胶片滤镜算法实现是3d lut. 那么3d lut  到底是个什么东西呢? 或者说它是用来做什么的? 长话短说,3d lut(全称 : 3D Lookup t ...

  9. 磨皮美颜算法 附完整C代码

    前言 2017年底时候写了这篇<集 降噪 美颜 虚化 增强 为一体的极速图像润色算法 附Demo程序> 这也算是学习过程中比较有成就感的一个算法. 自2015年做算法开始到今天,还有个把月 ...

随机推荐

  1. Eclipse导入Hadoop源码项目及编写Hadoop程序

    一 Eclipse导入Hadoop源码项目 基本步骤: 1)在Eclipse新建一个java项目[hadoop-1.2.1] 2)将Hadoop压缩包解压目录src下的core,hdfs,mapred ...

  2. Codeforces 842A Kirill And The Game【暴力,水】

    A. Kirill And The Game time limit per test:2 seconds memory limit per test:256 megabytes input:stand ...

  3. 在tomcat中布置项目的介绍(一)

    一:为什么要在tomcat中单独布置项目 因为上线到服务器上需要项目的功能之间彼此独立,这个以后我会细说. 二:简单的步骤一个都不能少 conf文件里的配置文件需要配置好:logback.xml文件会 ...

  4. [学习OpenCV攻略][004][播放AVI视频]

    cvCreateFileCapture(文件路径) 创建一个影音文件录像机,返回值为CvCapture类型,用于读取视频文件 cvQuerFrame(视频) 将下一帧视频文件载入内存,当CvCaptu ...

  5. 使用 Gacutil.exe 将.Net程序集添加到GAC的方法

    使用gacutil.exe工具安装:gacutil -i "要注册的dll文件全路径" "gacutil.exe”工具为.NET自带工具 ,需要注意的是:这个工具在.NE ...

  6. leak finder

    介绍 leak finder 是google开源团队发布了一个新的可以帮助web应用程序开发者在他们的JavaScript程序中找出内存泄露问题的工具: http://feedproxy.google ...

  7. mysql中使用show table status 查看表信息

    本文导读:在使用mysql数据库时,经常需要对mysql进行维护,查询每个库.每个表的具体使用情况,Mysql数据库可以通过执行SHOW TABLE STATUS命令来获取每个数据表的信息. 一.使用 ...

  8. linux服务器,svn认证失败,配置问题,防火墙等等

    之前自己还真没设置过SVN,今天亲自动手,错误百出,真是够头疼的.在网上随便找了一篇文章,就按照文章介绍开始安装.怎么安装和设置我就不说了,这里主要记录遇到的问题. 1.不知道该怎么设置 svn:// ...

  9. 阿里云部署SSL证书详解

    http://mp.weixin.qq.com/s/NV7Zad4DVEgzG2GCHYJVLw 查找中间证书 为了确保兼容到所有浏览器,我们必须在阿里云上部署中间证书,如果不部署证书,虽然安装过程可 ...

  10. 关于php存储cookie时path存放路径放的详细介绍

    以前在存储cookie时,没有用到path这个参数,最近开发中在存储cookie时,要用到path参数来决定存储的路径,结果一开始的时候一直取不到值,到最后详细看了一下path参数的介绍后,才顿悟.分 ...