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. es6的常用语法

    最常用的ES6特性 let, const, class, extends, super, arrow functions, template string, destructuring, defaul ...

  2. LVS集群之NAT模式实例(3)

    LVS集群NAT模式实例 1. 实验拓扑图 DS 必须有两块网卡,需要在上面做NAT. 2. 实验环境 3台CentOS6.4 64bit的服务器. 类型 IP DR eth0:10.20.73.20 ...

  3. java字符串比较

    我最近刚学java,今天编程的时候就遇到一个棘手的问题,就是关于判断两个字符串是否相等的问题.在编程中,通常比较两个字符串是否相同的表达式是"==",但在java中不能这么写.在j ...

  4. Django_ajax

    AJAX(Asynchronous Javascript And XML)翻译成中文就是"异步Javascript和XML".即使用Javascript语言与服务器进行异步交互,传 ...

  5. 【Java数据结构学习笔记之一】线性表的存储结构及其代码实现

    应用程序后在那个的数据大致有四种基本的逻辑结构: 集合:数据元素之间只有"同属于一个集合"的关系 线性结构:数据元素之间存在一个对一个的关系 树形结构:数据元素之间存在一个对多个关 ...

  6. Gym101522A Gym101522C Gym101522D

    Gym101522A A There are two popular formats for representing a date: day/month/year or month/day/year ...

  7. RMQ算法

    1. 概述 RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A ...

  8. Let the Balloon Rise(水)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1004 Let the Balloon Rise Time Limit: 2000/1000 MS (J ...

  9. Logstash+Kibana部署配置

    Logstash是一个接收,处理,转发日志的工具.支持系统日志,webserver日志,错误日志,应用日志,总之包括所有可以抛出来的日志类型. 典型的使用场景下(ELK): 用Elasticsearc ...

  10. c语言基础学习07

    ============================================================================= 涉及到的知识点有: 1.指针.指针的概念.指 ...